2012-01-21 33 views
81

tengo una clase que es IComparable:¿Cómo compara HashSet elementos para la igualdad?

public class a : IComparable 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 

    public a(int id) 
    { 
     this.Id = id; 
    } 

    public int CompareTo(object obj) 
    { 
     return this.Id.CompareTo(((a)obj).Id); 
    } 
} 

Cuando agrego una lista de objetos de esta clase a un conjunto de hash:

a a1 = new a(1); 
a a2 = new a(2); 
HashSet<a> ha = new HashSet<a>(); 
ha.add(a1); 
ha.add(a2); 
ha.add(a1); 

todo está bien y ha.count es 2, pero:

a a1 = new a(1); 
a a2 = new a(2); 
HashSet<a> ha = new HashSet<a>(); 
ha.add(a1); 
ha.add(a2); 
ha.add(new a(1)); 

Ahora ha.count es 3.

  1. Por qué no HashSet respecto a 's CompareTo método.
  2. ¿Es HashSet la mejor manera de tener una lista de objetos únicos?
+0

Añadir una implementación de '' IEqualityComparer en el constructor o implementan en la clase 'a'. https://msdn.microsoft.com/en-us/library/bb301504(v=vs.110).aspx – Jaider

Respuesta

82

Utiliza un IEqualityComparer<T> (EqualityComparer<T>.Default a menos que especifique uno diferente en la construcción).

Cuando agrega un elemento al conjunto, encontrará el código hash usando IEqualityComparer<T>.GetHashCode y almacenará el código hash y el elemento (después de verificar si el elemento ya está en el conjunto, por supuesto).

Para buscar un elemento, primero usará el IEqualityComparer<T>.GetHashCode para encontrar el código hash, luego para todos los elementos con el mismo código hash, usará IEqualityComparer<T>.Equals para comparar para igualdad real.

Eso significa que tiene dos opciones:

  • pase una costumbre IEqualityComparer<T> al constructor. Esta es la mejor opción si no puede modificar el T en sí mismo, o si desea una relación de igualdad no predeterminada (por ejemplo, "todos los usuarios con un ID de usuario negativo se consideran iguales"). Esto casi nunca se implementa en el tipo en sí (es decir, Foo no implementa IEqualityComparer<Foo>) pero en un tipo separado que solo se usa para las comparaciones.
  • Implementa la igualdad en el tipo en sí, anulando GetHashCode y Equals(object). Idealmente, implemente IEquatable<T> en el tipo, especialmente si se trata de un tipo de valor. Estos métodos serán llamados por el comparador de igualdad predeterminado.

Nota cómo nada de esto es en términos de una comparación ordenada - lo cual tiene sentido, ya que sin duda hay situaciones en las que se pueden especificar fácilmente la igualdad, pero no un orden total. Esto es todo lo mismo que Dictionary<TKey, TValue>, básicamente.

Si desea utilizar un juego que utiliza ordenar en lugar de sólo las comparaciones de igualdad, se debe utilizar SortedSet<T> de .NET 4 - que le permite especificar un IComparer<T> en lugar de un IEqualityComparer<T>. Esto utilizará IComparer<T>.Compare - que delegará en IComparable<T>.CompareTo o IComparable.CompareTo si está usando Comparer<T>.Default.

+6

+1 También tenga en cuenta la respuesta de @ tyriker (que IMO debería ser un comentario aquí) que señala que la forma más sencilla para aprovechar dicho 'IEqualityComparer .GetHashCode/Equals()' es implementar 'Equals' y' GetHashCode' en 'T' en sí mismo (y mientras lo hace, también implementaría la contraparte fuertemente tipada: -' bool IEtabletable .Equals (T other) ') –

+4

Aunque es muy precisa, esta respuesta puede ser un poco confusa, especialmente para los nuevos usuarios, ya que no indica claramente que para el caso más simple anular' Equals' y 'GetHashCode' es suficiente, como se mencionó en la respuesta de @tyriker. – BartoszKP

+0

Imo una vez que implemente 'IComparable' (o' IComparer' para ese asunto) no se le debería pedir que implemente la igualdad por separado (pero solo 'GetHashCode'). En cierto sentido, las interfaces de comparabilidad deberían heredar de las interfaces de igualdad. Entiendo los beneficios de rendimiento al tener dos funciones separadas (donde puede optimizar la igualdad por separado simplemente diciendo si algo es igual o no) pero aún así ... Muy confuso de lo contrario cuando haya especificado cuándo las instancias son iguales en la función 'CompareTo' y marco no considerará eso. – nawfal

9

HashSet utiliza Equals y GetHashCode().

CompareTo es para conjuntos ordenados.

Si desea objetos únicos, pero no le importa su orden de iteración, HashSet<T> es generalmente la mejor opción.

61

Aquí es aclaraciones sobre una parte de la respuesta que se ha dejado sin decir: El tipo de objeto de su HashSet<T> no tiene que poner en práctica IEqualityComparer<T> sino que simplemente tiene que anular Object.GetHashCode() y Object.Equals(Object obj).

lugar de esto:

public class a : IEqualityComparer<a> 
{ 
    public int GetHashCode(a obj) { /* Implementation */ } 
    public bool Equals(a obj1, a obj2) { /* Implementation */ } 
} 

Para ello:

public class a 
{ 
    public override int GetHashCode() { /* Implementation */ } 
    public override bool Equals(object obj) { /* Implementation */ } 
} 

Es sutil, pero esto me hizo tropezar durante la mayor parte del día tratando de conseguir HashSet funcione el camino es intencionado Y como han dicho otros, HashSet<a> terminará llamando al a.GetHashCode() y a.Equals(obj) según sea necesario cuando se trabaja con el conjunto.

+2

Buen punto. Por cierto, como mencioné en mi comentario sobre la respuesta de @JonSkeet, también debe implementar 'bool IEtabletable .Equals (T other)' para obtener una pequeña ganancia de eficiencia pero, lo que es más importante, el beneficio de claridad. Por razones obvias, además de la necesidad de implementar 'GetHashCode' junto con' IEtabletable ', el documento para IEtabletable menciona que, para mayor coherencia, también debe reemplazar el' object.Equals' por la consistencia –

+0

Intenté implementar esto. El 'ovveride getHashcode' funciona, pero' override bool equals' obtiene el error: no se encontró ningún método para anular. ¿alguna idea? – Stefanvds

+1

por supuesto, 'a obj' debe ser' object obj' – Stefanvds

1

constructor HashSet recibe el objeto que implementa IEqualityComparer para agregar un nuevo objeto. si utilice el método whant en HashSet que overrride Nead iguales, GetHashCode

namespace HashSet 
{ 
    public class Employe 
    { 
     public Employe() { 
     } 

     public string Name { get; set; } 

     public override string ToString() { 
      return Name; 
     } 

     public override bool Equals(object obj) { 
      return this.Name.Equals(((Employe)obj).Name); 
     } 

     public override int GetHashCode() { 
      return this.Name.GetHashCode(); 
     } 
    } 

    class EmployeComparer : IEqualityComparer<Employe> 
    { 
     public bool Equals(Employe x, Employe y) 
     { 
      return x.Name.Trim().ToLower().Equals(y.Name.Trim().ToLower()); 
     } 

     public int GetHashCode(Employe obj) 
     { 
      return obj.Name.GetHashCode(); 
     } 
    } 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      HashSet<Employe> hashSet = new HashSet<Employe>(new EmployeComparer()); 
      hashSet.Add(new Employe() { Name = "Nik" }); 
      hashSet.Add(new Employe() { Name = "Rob" }); 
      hashSet.Add(new Employe() { Name = "Joe" }); 
      Display(hashSet); 
      hashSet.Add(new Employe() { Name = "Rob" }); 
      Display(hashSet); 

      HashSet<Employe> hashSetB = new HashSet<Employe>(new EmployeComparer()); 
      hashSetB.Add(new Employe() { Name = "Max" }); 
      hashSetB.Add(new Employe() { Name = "Solomon" }); 
      hashSetB.Add(new Employe() { Name = "Werter" }); 
      hashSetB.Add(new Employe() { Name = "Rob" }); 
      Display(hashSetB); 

      var union = hashSet.Union<Employe>(hashSetB).ToList(); 
      Display(union); 
      var inter = hashSet.Intersect<Employe>(hashSetB).ToList(); 
      Display(inter); 
      var except = hashSet.Except<Employe>(hashSetB).ToList(); 
      Display(except); 

      Console.ReadKey(); 
     } 

     static void Display(HashSet<Employe> hashSet) 
     { 
      if (hashSet.Count == 0) 
      { 
       Console.Write("Collection is Empty"); 
       return; 
      } 
      foreach (var item in hashSet) 
      { 
       Console.Write("{0}, ", item); 
      } 
      Console.Write("\n"); 
     } 

     static void Display(List<Employe> list) 
     { 
      if (list.Count == 0) 
      { 
       Console.WriteLine("Collection is Empty"); 
       return; 
      } 
      foreach (var item in list) 
      { 
       Console.Write("{0}, ", item); 
      } 
      Console.Write("\n"); 
     } 
    } 
} 
Cuestiones relacionadas