c# - keys - ¿Cómo se implementa GetHashCode para la estructura con dos cadenas, cuando ambas cadenas son intercambiables?
hash table array c# (14)
Tengo una estructura en C #:
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 anular la función GetHashCode para esta estructura?
Como regla general, una forma simple de generar un código de hash para una clase es XOR todos los campos de datos que pueden participar en la generación del código de hash (teniendo cuidado de comprobar nulo como lo señalaron 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.
Si puede hacer suposiciones sobre el uso de su clase, quizás pueda mejorar su función 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 esto claramente no pretende ser un ejemplo del mundo real, puede valer la pena señalar que: - Este es probablemente un mal ejemplo de uso de una estructura: una estructura normalmente debería tener una semántica de valores, que no parece ser el caso aquí. - El uso de propiedades con setters para generar un código hash también está pidiendo problemas.
Ah sí, como señaló Gary Shutler:
return str1.GetHashCode() + str2.GetHashCode();
Puede desbordar Podrías tratar de transmitir todo el tiempo que sugiriera Artem, o podrías rodear la declaración en la palabra clave no verificada:
return unchecked(str1.GetHashCode() + str2.GetHashCode());
Demasiado complicado, y olvida nulos, etc. Esto se usa para cosas como el cubo, por lo que puede salirse con la suya
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á sesgado al suponer que str1 no es probable que sea común en una proporción inusualmente grande de instancias.
Muchas posibilidades P.ej
return str1.GetHashCode() ^ str1.GetHashCode()
Ordenarlos, luego concatenarlos:
return ((str1.CompareTo(str2) < 1) ? str1 + str2 : str2 + str1) .GetHashCode();
Prueba este:
(((long)str1.GetHashCode()) + ((long)str2.GetHashCode())).GetHashCode()
Se supone que el resultado de GetHashCode es:
- Tan rápido como sea posible.
- Tan único como sea posible.
Teniendo eso en mente, iría 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: Olvidé los nulos. Código arreglado
Siguiendo las líneas, ReSharper sugiere:
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 un primo de tamaño suficiente para provocar que la variable de resultado se desborde y mezcle un poco los bits del hash, 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.
Tal vez algo como str1.GetHashCode () + str2.GetHashCode ()? o (str1.GetHashCode () + str2.GetHashCode ()) / 2? De esta forma, sería lo mismo independientemente de si se intercambian str1 y str2 ....
Una forma general simple es hacer esto:
return string.Format("{0}/{1}", str1, str2).GetHashCode();
A menos que tenga estrictos requisitos de rendimiento, esto es lo más fácil que puedo pensar y con frecuencia uso 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.
Vea la respuesta de Jon Skeet : las operaciones binarias como ^
no son buenas, ¡a menudo generarán hash colisionante!
MSDN :
Una función hash debe tener las siguientes propiedades:
- Si dos objetos se comparan como iguales, el método
GetHashCode
para cada objeto debe devolver el mismo valor. Sin embargo, si dos objetos no se pueden comparar como iguales, los métodosGetHashCode
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 determine el valor de retorno del métodoEquals
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.
Tomándolo en cuenta la forma correcta es:
return str1.GetHashCode() ^ str2.GetHashCode()
^
puede ser sustituido con otra operación conmutativa
public override int GetHashCode()
{
unchecked
{
return (str1 ?? String.Empty).GetHashCode() +
(str2 ?? String.Empty).GetHashCode();
}
}
Usar el operador ''+'' podría ser mejor que usar ''^'', porque aunque explícitamente quieras (''AA'', ''BB'') y (''BB'', ''AA'') explícitamente ser el mismo, es posible que no quieras ( ''AA'', ''AA'') y (''BB'', ''BB'') para ser el mismo (o todos los pares iguales para el caso).
La regla ''tan rápido como sea 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 medir explícitamente estoy dispuesto para aventurar una suposición de que la diferencia no sería tan grande como para preocuparse a menos que espere muchos nulos.
public override int GetHashCode()
{
unchecked
{
return(str1 != null ? str1.GetHashCode() : 0) ^ (str2 != null ? str2.GetHashCode() : 0);
}
}