2009-05-21 20 views
56

Estoy tratando de crear una función rápida de hashcode para una clase de número complejo (a + b) en C#.Crear un hashcode de dos números

He visto repetidamente el método a.GetHashcode()^b.GetHashCode(). Pero esto dará el mismo código hash para (a,b) y (b,a).

¿Hay algún algoritmo estándar para hacer esto y hay alguna función en el marco .Net para ayudar?

+0

http://stackoverflow.com/questions/682438/hash-function-providing-unique-uint-from-an-inate-coordinate-pair/682617#682617 –

Respuesta

76

Mi forma habitual de crear un código hash para un conjunto arbitrario de elementos hashable:

int hash = 23; 
hash = hash * 31 + item1Hash; 
hash = hash * 31 + item2Hash; 
hash = hash * 31 + item3Hash; 
hash = hash * 31 + item4Hash; 
hash = hash * 31 + item5Hash; 
// etc 

En su caso item1Hash podría ser sólo a y item2Hash podría ser sólo b.

Los valores de 23 y 31 son relativamente poco importantes, siempre y cuando sean primos (o al menos coprimidos).

Obviamente todavía habrá colisiones, pero que no se quede en los problemas desagradables normales de:

hash(a, a) == hash(b, b) 
hash(a, b) == hash(b, a) 

Si usted sabe más acerca de lo que los valores reales de a y b es probable que se pueda probablemente sea mejor, pero esta es una buena implementación inicial que es fácil de recordar e implementar. Tenga en cuenta que si hay alguna posibilidad de que construya el conjunto con "verificar derrame/subdesbordamiento aritmético", debe ponerlo todo en un bloque sin marcar. (Desbordamiento está muy bien para este algoritmo.)

+0

" siempre y cuando sean primos (o al menos coprime) "- Hubiera pensado que el estado inicial puede ser cualquier cosa (si múltiplos de 31 son malos, entonces, ¿qué sucede cuando alcanzas ese valor por casualidad en parte a través del ¿cálculo?). Entonces el multiplicador solo tiene que ser impar (para evitar que los primeros valores no tengan efecto en los bits bajos del resultado). Y no 1, para evitar la conmutatividad. ¿Me estoy perdiendo el punto por completo? –

+0

No me gustaría decir que he seguido la lógica de por qué los números deben ser coprimarios, pero ese es el consejo que siempre he dado para este patrón por gente más sabia que yo (como Josh Bloch). –

+0

No puedo culparte por hacer lo que el jefe del jefe de tu jefe te dice ;-) Aquí está él escribiendo un método hashCode con el estado inicial 0, aunque solo como parte de un ejemplo que ilustra algo completamente diferente: http://209.85.229.132/search ? q = caché: 3H-Bb8E4sDEJ: developers.sun.com/learning/javaoneonline/2007/pdf/TS-2689.pdf+josh+bloch+hashcode+coprime&cd=3&hl=en&ct=clnk&gl=uk&client=firefox-a. Tal vez debería leer Java efectivo ... –

5

Qué tal esto:

(a.GetHashcode() + b).GetHashcode() 

le da un código diferente para (a, b) y (b, a) además de que no es tan elegante.

+9

No siempre es correcto. Para Int32s, x.GetHashCode() simplemente devuelve x. Entonces (a.GetHasCode() + b) .GetHashCode() es solo a + b. – hwiechers

+0

En el caso en que ayb sean Int32. – hwiechers

13

Aquí hay un posible enfoque que tiene en cuenta el orden. (El segundo método se define como un método de extensión.)

public int GetHashCode() 
{ 
    return a.GetHashcode()^b.GetHashcode().RotateLeft(16); 
} 

public static uint RotateLeft(this uint value, int count) 
{ 
    return (value << count) | (value >> (32 - count)) 
} 

Sin duda, sería interesante ver cómo la clase de .NET 4.0 Complex lo hace.

+1

Esta es la mejor respuesta si los valores enteros están sesgados, p. si tienden a ser pequeños porque son claves primarias autogeneradas en una base de datos. La llamada a a.GetHashCoce() y b.GetHashCode() no es necesaria ya que simplemente devolverá el valor de a y b respectivamente (creo que se trata de un detalle de implementación actual en lugar de un comportamiento documentado). –

+1

Las llamadas a GetHashCode() ciertamente son necesarias si a y b son cualquier cosa que no sea 'int' (como' uint') debido al tipo de devolución de GetHashCode() en la clase contenedora. – Neo

+0

Buena solución; Me gusta el hecho de que esto mantiene todo ordenado y predecible. Esto parece una solución "correcta y completa". Sin embargo, un comentario: el método de extensión no funcionó, en el sentido de que el compilador no era lo suficientemente inteligente como para forzar el valor 'int' de' GetHashCode() 'a un' uint'. No quería hacer las ediciones de tu código, porque parecía que solo agregaría ruido. –

11

Una forma estándar es la siguiente:

hashcode = 23 
hashcode = (hashcode * 37) + v1 
hashcode = (hashcode * 37) + v2 

23 y 37 son primos entre sí, pero se pueden utilizar otros números también.

0

Todo eso depende de lo que estés tratando de lograr. Si los hash son para estructuras hash como Dictionary, entonces tiene que tasa de colisión de balance y velocidad de hash. Para tener un hash perfecto sin colisión, será más lento. De manera similar, el algoritmo hash más rápido tendrá más colisiones relativamente. Encontrar el equilibrio perfecto es la clave aquí. También debe tener en cuenta qué tan grande puede ser su hachís efectivo, y si hash debe ser reversible! El enfoque de Noldorin le da un hash perfecto (no lea colisión) si sus partes reales e imaginarias de su número complejo son siempre positivas. Esto servirá incluso para números negativos si estás de acuerdo con las raras colisiones. Pero me preocupa el rango de valores que puede ofrecer, bastante grande para mi gusto.

Si busca los valores hash perfectos (por razones académicas o de investigación) que deberían funcionar incluso para números negativos, puede see this solution (y una serie de otras soluciones en el mismo hilo). En mis pruebas, es más rápido y utiliza el espacio mejor que cualquier otro que he visto.

5

@JonSkeet proporciona un algoritmo justo y general para calcular un código hash a partir de n códigos hash pero supone que ya sabe qué miembros de un objeto deben ser hash, sabe qué hacer con los miembros nulos y omite una implementación para n elementos arbitrarios. Así que ampliamos su respuesta:

  1. Solo las propiedades y los campos públicos, inmutables deben contribuir al código hash de un objeto. Deberían ser públicos (o isomórficos para el público) ya que deberíamos poder contar con dos objetos con la misma superficie visible con el mismo código hash (haciendo alusión a la relación entre igualdad de objeto e igualdad de código hash), y deberían ser inmutables desde el código hash de un objeto nunca debe cambiar en su tiempo de vida (¡ya que puede terminar con un objeto en la ranura incorrecta de una tabla hash!).
  2. miembros nulos deben almohadilla, como una constante, como 0
  3. @ algoritmo de JonSkeet es un ejemplo de libro de texto para la aplicación de la función de la programación funcional de orden superior generalmente llamado fold (Aggregate en C# LINQ), donde 23 es nuestra semilla y <hash accumulator> * 31 + <current item hash> es nuestra función de plegado:

En F #

let computeHashCode items = 
    items 
    |> Seq.map (fun item -> if item = null then 0 else item.GetHashCode()) 
    |> Seq.fold (fun hash itemHash -> hash * 31 + itemHash) 23 

En C#

Func<IEnumerable<Object>, int> computeHashCode = items => 
    items 
    .Select(item => item == null ? 0 : item.GetHashCode()) 
    .Aggregate(23, (hash, itemHash) => hash * 31 + itemHash); 
Cuestiones relacionadas