2010-09-30 16 views
5

Estoy siguiendo un previous post en stackoverflow para eliminar duplicados de una lista en C#.eliminando duplicados de una lista C#

Si es <T> algún usuario tipo definido como:

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

La sugerido (HashMap) no quita duplicado. Creo que tengo que redefinir algún método para comparar dos objetos, ¿o no?

Respuesta

18

A HashSet<T>elimina eliminar duplicados, porque es un conjunto ... pero solo cuando su tipo define la igualdad de forma apropiada.

sospecho por "duplicar" quiere decir "un objeto con valores de campo igual a otro objeto" - tiene que anular Equals/GetHashCode para que eso trabajo, y/o implementar IEquatable<Contact> ... o usted podría proporcionar una IEqualityComparer<Contact> al constructor HashSet<T>.

En lugar de utilizar un HashSet<T> que podría simplemente llamar al método de extensión Distinct LINQ. Por ejemplo:

list = list.Distinct().ToList(); 

Pero de nuevo, tendrá que proporcionar una definición adecuada de la igualdad, de alguna manera u otra.

Aquí hay una implementación de muestra. Observe cómo lo he hecho inmutable (la igualdad es impar con los tipos mutables, porque dos objetos pueden ser iguales un minuto y no igual al siguiente) y hicieron los campos privados, con propiedades públicas. Finalmente, he sellado la clase: los tipos inmutables generalmente deben sellarse, y hace que la igualdad sea más fácil de hablar.

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: Bueno, en respuesta a las peticiones de una explicación de la aplicación GetHashCode():

  • queremos combinar los códigos hash de las propiedades de este objeto
  • No estamos comprobando para la nulidad en cualquier lugar, por lo que debemos suponer que algunos de ellos pueden ser nulos. EqualityComparer<T>.Default siempre maneja esto, lo cual es bueno ... así que lo estoy usando para obtener un código hash de cada campo.
  • El enfoque "agregar y multiplicar" para combinar varios códigos hash en uno es el estándar recomendado por Josh Bloch. Hay muchos otros algoritmos de hashing de propósito general, pero este funciona bien para la mayoría de las aplicaciones.
  • No sé si está compilando en un contexto verificado de forma predeterminada, por lo que he puesto el cálculo en un contexto sin marcar. Nosotros realmente no importa si la multiplicación/agregación repetida conduce a un desbordamiento, porque no estamos buscando una "magnitud" como tal ... solo un número que podemos alcanzar repetidamente para objetos iguales.

Dos formas alternativas de manejo de nulidad, por cierto:

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

Exactamente lo que escribí (si no hubiera sido demasiado tarde :-) –

+1

Esa implementación de GetHashCode() debe venir con comentarios explicativos. Mi cerebro no funciona en tu nivel. –

+0

¿Puede agradar al método GetHashCode, no parece seguir eso. – 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); 
    } 
} 

da

1:John 
2:Sylvia 
1

Para esta tarea no lo hago Necesario ily piensa que la implementación de IComparable es la solución obvia. Es posible que desee ordenar y probar la exclusividad de muchas maneras diferentes.

Yo estaría a favor de la implementación de 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(); 
    } 
} 

y luego usar System.Linq.Enumerable.Distinct (suponiendo que está utilizando al menos .NET 3,5)

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

PS. Hablando de HashSet<> Tenga en cuenta que HashSet<> toma un IEqualityComparer<> como un parámetro de constructor.

Cuestiones relacionadas