2008-09-16 11 views
63

que tienen una estructura en C#:¿Cómo se implementa GetHashCode para la estructura con dos cuerdas, cuando ambas cadenas son intercambiables

public struct UserInfo 
{ 
    public string str1 
    { 
    get; 
    set; 
    } 

    public string str2 
    { 
    get; 
    set; 
    } 
} 

La única regla es que UserInfo(str1="AA", str2="BB").Equals(UserInfo(str1="BB", str2="AA"))

cómo reemplazar la función GetHashCode para esta estructura?

+0

posible duplicado de [Fast String Hashing Algorithm con bajas tasas de colisión con 32 bits enteros] (http://stackoverflow.com/questions/114085/fast-string-hashing-algorithm-with-low-collision-rates-with -32-bit-integer) – nawfal

+3

@nawfal, ¿no debería ser al revés? Mi pregunta fue publicada el 16 de septiembre de 2008, pero la que usted propuso fue publicada el 22 de septiembre de 2008. – Graviton

Respuesta

61

MSDN:

una función hash debe tener las siguientes propiedades:

  • Si dos objetos comparan como iguales, el método de GetHashCode para cada objeto debe devolver el mismo valor. Sin embargo, si dos objetos no se pueden comparar como iguales, los métodos GetHashCode para los dos objetos no tienen que devolver valores diferentes.
  • El método GetHashCode para un objeto debe devolver consistentemente el mismo código hash siempre que no haya ninguna modificación en el estado del objeto que determina el valor de retorno del método Equals del objeto. Tenga en cuenta que esto es cierto solo para la ejecución actual de una aplicación, y que se puede devolver un código hash diferente si la aplicación se ejecuta nuevamente.
  • Para obtener el mejor rendimiento, una función hash debe generar una distribución aleatoria para todas las entradas.

Si lo toma en cuenta forma correcta es:

return str1.GetHashCode()^str2.GetHashCode() 

^ pueden estar sustituidos con otra operación conmutativa

+0

¿No debería ser return str1.GetHashCode ()^str2.GetHashCode(); – roomaroo

+2

Además, no considera valores nulos. –

+1

roomaroo, gracias por la corrección. por supuesto es str1^str2 – aku

0

Muchas posibilidades. P.ej.

return str1.GetHashCode()^str1.GetHashCode()

0

Tal vez algo así como str1.GetHashCode() + str2.GetHashCode()? o (str1.GetHashCode() + str2.GetHashCode())/2? De esta manera, sería el mismo, independientemente de si cadena1 y cadena2 se intercambian ....

1

Pruebe éste:

(((long)str1.GetHashCode()) + ((long)str2.GetHashCode())).GetHashCode() 
0

Ordenar ellos, entonces concatenar:

 
return ((str1.CompareTo(str2) < 1) ? str1 + str2 : str2 + str1) 
    .GetHashCode(); 
+2

Esto hará que su método GetHashCode trabaje mucho. Los códigos Hash están destinados a ser rápidos. Desde MSDN: "Una función hash se usa para generar rápidamente un número (código hash) que corresponde al valor de un objeto". Asignar una nueva cadena parece una mala idea dentro de una función hash. – Wilka

0

de GetHashCode se supone que el resultado es:

  1. Lo más rápido posible.
  2. Tan único como sea posible.

Teniendo en cuenta aquellos, me gustaría ir con algo como esto:

if (str1 == null) 
    if (str2 == null) 
     return 0; 
    else 
     return str2.GetHashCode(); 
else 
    if (str2 == null) 
     return str1.GetHashCode(); 
    else 
     return ((ulong)str1.GetHashCode() | ((ulong)str2.GetHashCode() << 32)).GetHashCode(); 

Editar: ¿Ha olvidado los nulos. Código arreglado

+1

La única regla es que UserInfo (str1 = "AA", str2 = "BB"). Igual (UserInfo (str1 = "BB", str2 = "AA")) –

2

Ah sí, como Gary Shutler señaló:

return str1.GetHashCode() + str2.GetHashCode(); 

Puede desbordarse. Usted podría intentar la fundición hasta el tiempo que sugirió Artem, o usted podría rodear la declaración en la palabra clave sin control:

return unchecked(str1.GetHashCode() + str2.GetHashCode()); 
-1

demasiado complicado, y se olvida de los nulos, etc. Esto se utiliza para cosas como bucketing, por lo que puede salirse con algo como

if (null != str1) { 
    return str1.GetHashCode(); 
} 
if (null != str2) { 
    return str2.GetHashCode(); 
} 
//Not sure what you would put here, some constant value will do 
return 0; 

Esto está sesgada por el supuesto de que str1 no es probable que sea común en una inusual gran proporción de casos.

+0

Esto no cumple la condición de que el orden de str1 y str2 no importe. ("A", "B") y ("B", "A") producen diferentes hashcodes. –

+0

6.5 años después? ¿Y a qué condición te estás refiriendo? Esta es la discusión sobre la generación de un código hash para una estructura que contiene 2 cadenas, no para lo que sucede cuando se comparan 2 cadenas. –

+0

Las estructuras ("A", "B") y ("B", "A") deben considerarse iguales. Por lo tanto, sus códigos hash deben ser iguales. Pero ("A", "B") produce el código hash de "A", y ("B", "A") produce el código hash de "B", que no es igual. –

3
public override int GetHashCode() 
{  
    unchecked  
    {   
     return(str1 != null ? str1.GetHashCode() : 0)^(str2 != null ? str2.GetHashCode() : 0);  
    } 
} 
+7

¿Por qué no seleccionado? xor no puede desbordarse. –

13
public override int GetHashCode() 
{ 
    unchecked 
    { 
     return (str1 ?? String.Empty).GetHashCode() + 
      (str2 ?? String.Empty).GetHashCode(); 
    } 
} 

Usando el operador '+' podría ser mejor que usar '^', porque a pesar de que desee de forma explícita ('AA', BB ') y ('BB', 'AA') a explícitamente sea el mismo, puede que no quieras ('AA', 'AA') y ('BB', 'BB') ser el mismo (o todos los pares iguales para el caso).

La regla 'lo más rápido posible' no se cumple completamente en esta solución porque en el caso de nulos esto realiza un 'GetHashCode()' en la cadena vacía en lugar de devolver inmediatamente una constante conocida, pero incluso sin explícitamente midiendo Estoy dispuesto a arriesgarme a suponer que la diferencia no sería tan grande como para preocuparse a menos que espere muchos nulos.

5
  1. Como regla general, una forma sencilla de generar un código hash para una clase es XOR todos los campos de datos que pueden participar en la generación del código hash (teniendo cuidado de comprobar NULL como han señalado otros) . Esto también cumple con el requisito (¿artificial?) De que los códigos hash para UserInfo ("AA", "BB") y UserInfo ("BB", "AA") son los mismos.

  2. Si puede hacer suposiciones sobre el uso de su clase, quizás pueda mejorar su función de hash. Por ejemplo, si es común que str1 y str2 sean lo mismo, XOR puede no ser una buena opción. Pero si str1 y str2 representan, por ejemplo, nombre y apellido, XOR es probablemente una buena opción.

Aunque esta claro que no está destinado a ser un ejemplo del mundo real, puede ser la pena señalar que: - Esta es probablemente un mal ejemplo de uso de una estructura: una estructura deberían tener, por la semántica de valor , que no parece ser el caso aquí. - El uso de propiedades con setters para generar un código hash también está causando problemas.

+0

Hmm, ¿por qué crees que su estructura no tiene una semántica de valores? ¿Y podrías ampliar tu última oración? –

24

Ver Jon Skeet's answer - operaciones binarias como ^ no son buenas, ¡a menudo generarán hash colisionante!

+4

pero Jon dice que es malo porque hará exactamente lo que OP quiere. 'F (a, b) == F (b, a)' ... – Noctis

3

Yendo a lo largo del ReSharper líneas está sugiriendo:

public int GetHashCode() 
{ 
    unchecked 
    { 
     int hashCode; 

     // String properties 
     hashCode = (hashCode * 397)^(str1!= null ? str1.GetHashCode() : 0); 
     hashCode = (hashCode * 397)^(str2!= null ? str1.GetHashCode() : 0); 

     // int properties 
     hashCode = (hashCode * 397)^intProperty; 
     return hashCode; 
    } 
} 

397 es primo de tamaño suficiente para causar la variable de resultado a desbordarse y mezclar los bits del hash poco, proporcionando una mejor distribución de los códigos hash. De lo contrario, no hay nada especial en 397 que lo distinga de otros números primos de la misma magnitud.

+0

Este código hash no cumple con los requisitos de OP: la única regla es que UserInfo (str1 = "AA", str2 = "BB"). Igual (UserInfo (str1 = "BB", str2 = "AA")) –

2

Un simple generales manera es hacer esto:

return string.Format("{0}/{1}", str1, str2).GetHashCode(); 

A menos que tenga los estrictos requisitos de rendimiento, este es el más fácil de lo que puedo pensar y frecuentemente utilizar este método cuando necesito una clave compuesta. Maneja bien los casos null y no provocará (m) colisiones hash (en general). Si espera '/' en sus cadenas, simplemente elija otro separador que no espera.

+0

Muy simple de hecho. Esto se puede simplificar en C# 6.0 para simplemente 'return $" {str1}/{str2} ". GetHashCode();'. Consulte [Interpolación de cadenas] (https://msdn.microsoft.com/en-us/library/dn961160.aspx) – styfle

+0

No es seguro, ¿qué pasa si str1 = "a/b" y str2 = ""? Esto tendría el mismo hash que str1 = "a" y str2 = "b /". –

+1

@ErwinMayer use un carácter de separación que sepa que no está en sus cadenas. Además, GetHashCode no está obligado a devolver siempre valores únicos. Se utiliza como una optimización para evitar llamar a 'Iguales' con demasiada frecuencia (la comparación exacta suele ser más costosa). –

Cuestiones relacionadas