2010-09-30 21 views
5

Sto seguendo uno previous post su StackOverflow sulla rimozione di duplicati da un elenco in C#.rimozione di duplicati da un elenco C#

Se <T> è qualche utente tipo definito come:

class Contact 
{ 
    public string firstname; 
    public string lastname; 
    public string phonenum; 
} 

Il suggerito (HashMap) non rimuove duplicato. Penso, devo ridefinire un metodo per confrontare due oggetti, vero?

risposta

18

A HashSet<T>fa rimuovere i duplicati, perché è un set ... ma solo quando il tipo definisce l'uguaglianza in modo appropriato.

Ho il sospetto da "duplicato" si intende "un oggetto con valori di campo uguale ad un altro oggetto" - è necessario eseguire l'override Equals/GetHashCode per questo al lavoro, e/o implementare IEquatable<Contact> ... o si potrebbe fornire un IEqualityComparer<Contact> al costruttore .

Invece di utilizzare un HashSet<T> si potrebbe basta chiamare il metodo di estensione Distinct LINQ. Ad esempio:

Ma ancora una volta, è necessario fornire una definizione appropriata di uguaglianza, in un modo o nell'altro.

Ecco un esempio di implementazione. Nota come l'ho reso immutabile (l'uguaglianza è dispari con i tipi mutabili, perché due oggetti possono essere uguali un minuto e non uguali al successivo) e resi i campi privati, con proprietà pubbliche. Infine, ho sigillato la classe - i tipi immutabili dovrebbero essere generalmente sigillati, e rende più facile parlare di uguaglianza.

using System; 
using System.Collections.Generic; 

public sealed class Contact : IEquatable<Contact> 
{ 
    private readonly string firstName; 
    public string FirstName { get { return firstName; } } 

    private readonly string lastName; 
    public string LastName { get { return lastName; } } 

    private readonly string phoneNumber; 
    public string PhoneNumber { get { return phoneNumber; } } 

    public Contact(string firstName, string lastName, string phoneNumber) 
    { 
     this.firstName = firstName; 
     this.lastName = lastName; 
     this.phoneNumber = phoneNumber; 
    } 

    public override bool Equals(object other) 
    { 
     return Equals(other as Contact); 
    } 

    public bool Equals(Contact other) 
    { 
     if (object.ReferenceEquals(other, null)) 
     { 
      return false; 
     } 
     if (object.ReferenceEquals(other, this)) 
     { 
      return true; 
     } 
     return FirstName == other.FirstName && 
       LastName == other.LastName && 
       PhoneNumber == other.PhoneNumber; 
    } 

    public override int GetHashCode() 
    { 
     // Note: *not* StringComparer; EqualityComparer<T> 
     // copes with null; StringComparer doesn't. 
     var comparer = EqualityComparer<string>.Default; 

     // Unchecked to allow overflow, which is fine 
     unchecked 
     { 
      int hash = 17; 
      hash = hash * 31 + comparer.GetHashCode(FirstName); 
      hash = hash * 31 + comparer.GetHashCode(LastName); 
      hash = hash * 31 + comparer.GetHashCode(PhoneNumber); 
      return hash; 
     } 
    } 
} 

EDIT: Okay, in risposta alle richieste per una spiegazione del GetHashCode() realizzazione:

  • vogliamo unire i codici hash delle proprietà di questo oggetto
  • Stiamo non controllare per nullità ovunque, quindi dovremmo assumere che alcuni di essi potrebbero essere nulli. EqualityComparer<T>.Default gestisce sempre questo, che è bello ... quindi lo sto usando per ottenere un codice hash di ogni campo.
  • L'approccio "aggiungi e moltiplica" per combinare più codici hash in uno è quello standard consigliato da Josh Bloch. Esistono molti altri algoritmi di hashing generici, ma questo funziona bene per la maggior parte delle applicazioni.
  • Non so se si sta compilando in un contesto controllato per impostazione predefinita, quindi ho messo il calcolo in un contesto non controllato. Noi davvero non importa se il ripetuto moltiplicare/aggiungere porta a un overflow, perché non stiamo cercando una "grandezza" in quanto tale ... solo un numero che possiamo raggiungere ripetutamente per oggetti uguali.

Due modi alternativi di gestire la nullità, tra l'altro:

public override int GetHashCode() 
{ 
    // Unchecked to allow overflow, which is fine 
    unchecked 
    { 
     int hash = 17; 
     hash = hash * 31 + (FirstName ?? "").GetHashCode(); 
     hash = hash * 31 + (LastName ?? "").GetHashCode(); 
     hash = hash * 31 + (PhoneNumber ?? "").GetHashCode(); 
     return hash; 
    } 
} 

o

public override int GetHashCode() 
{ 
    // Unchecked to allow overflow, which is fine 
    unchecked 
    { 
     int hash = 17; 
     hash = hash * 31 + (FirstName == null ? 0 : FirstName.GetHashCode()); 
     hash = hash * 31 + (LastName == null ? 0 : LastName.GetHashCode()); 
     hash = hash * 31 + (PhoneNumber == null ? 0 : PhoneNumber.GetHashCode()); 
     return hash; 
    } 
} 
+0

Esattamente quello che avevo scritto (se non fossi stato troppo tardi :-) –

+1

Quella implementazione di GetHashCode() deve venire con commenti esplicativi. Il mio cervello non funziona al tuo livello. –

+0

Potete per favore il metodo GetHashCode, non mi sembra di seguirlo. – Sandy

1
class Contact { 
    public int Id { get; set; } 
    public string Name { get; set; } 

    public override string ToString() 
    { 
     return string.Format("{0}:{1}", Id, Name); 
    } 

    static private IEqualityComparer<Contact> comparer; 
    static public IEqualityComparer<Contact> Comparer { 
     get { return comparer ?? (comparer = new EqualityComparer()); } 
    } 

    class EqualityComparer : IEqualityComparer<Contact> { 
     bool IEqualityComparer<Contact>.Equals(Contact x, Contact y) 
     { 
      if (x == y) 
       return true; 

      if (x == null || y == null) 
       return false; 

      return x.Name == y.Name; // let's compare by Name 
     } 

     int IEqualityComparer<Contact>.GetHashCode(Contact c) 
     { 
      return c.Name.GetHashCode(); // let's compare by Name 
     } 
    } 
} 

class Program { 
    public static void Main() 
    { 
     var list = new List<Contact> { 
      new Contact { Id = 1, Name = "John" }, 
      new Contact { Id = 2, Name = "Sylvia" }, 
      new Contact { Id = 3, Name = "John" } 
     }; 

     var distinctNames = list.Distinct(Contact.Comparer).ToList(); 
     foreach (var contact in distinctNames) 
      Console.WriteLine(contact); 
    } 
} 

1:John 
2:Sylvia 
1

Per questo compito non lo faccio necessar pensa che l'implementazione di IComparable sia la soluzione più ovvia. Potresti voler ordinare e testare l'unicità in molti modi diversi.

vorrei favorire l'attuazione di un IEqualityComparer<Contact>:

sealed class ContactFirstNameLastNameComparer : IEqualityComparer<Contact> 
{ 
    public bool Equals (Contact x, Contact y) 
    { 
    return x.firstname == y.firstname && x.lastname == y.lastname; 
    } 

    public int GetHashCode (Contact obj) 
    { 
    return obj.firstname.GetHashCode()^obj.lastname.GetHashCode(); 
    } 
} 

e quindi utilizzare System.Linq.Enumerable.Distinct (supponendo che si sta utilizzando almeno NET 3,5)

var unique = contacts.Distinct (new ContactFirstNameLastNameComparer()).ToArray(); 

PS. A proposito di HashSet<> Si noti che HashSet<> prende un IEqualityComparer<> come parametro costruttore.

Problemi correlati