Obtener hash de una lista de cadenas independientemente del orden


Me gustaría escribir una función GetHashCodeOfList() que devuelve un código hash de una lista de cadenas, independientemente del orden. Dadas 2 listas con las mismas cadenas deben devolver el mismo hash-code.

ArrayList list1 = new ArrayList()    
list1.Add("String1");
list1.Add("String2");
list1.Add("String3");    

ArrayList list2 = new ArrayList()    
list2.Add("String3");    
list2.Add("String2"); 
list2.Add("String1");

GetHashCodeOfList(list1) = GetHashCodeOfList(list2) //this should be equal.

Tuve algunos pensamientos:

  1. Primero puedo ordenar la lista, luego combinar la lista ordenada en 1 cadena larga y luego llamar GetHashCode(). Sin embargo, la clasificación es una operación lenta.

  2. Puedo obtener el hash de cada cadena individual (llamando a string.GetHashCode()) en la lista, entonces multiplicando todos los hashes y llamando a Mod UInt32.MaxValue. Por ejemplo: "String1".GetHashCode() * "String2".GetHashCode * … MOD UInt32.MaxValue. Pero esto resulta en un desbordamiento de número.

¿Alguien tiene algún pensamiento?

Gracias de antemano por su ayuda.

Author: nawfal, 2009-03-22

3 answers

Hay varios enfoques diferentes aquí las dos categorías principales, cada uno típicamente con sus propios beneficios y desventajas, en términos de eficacia y rendimiento. Probablemente es mejor elegir el algoritmo más simple para cualquier aplicación y solo usar las variantes más complejas si es necesario para cualquier situación.

Tenga en cuenta que estos ejemplos utilizan EqualityComparer<T>.Default ya que se ocupará de los elementos null limpiamente. Usted podría hacer mejor que cero para null si se desea. Si T es constrained to struct it is also unnecessary. Puede ho la búsqueda EqualityComparer<T>.Default fuera de la función si así lo desea.

Operaciones conmutativas

Si usa operaciones en los hashcodes de las entradas individuales que son conmutativas entonces esto conducirá al mismo resultado final independientemente del orden.

Hay varias opciones obvias sobre los números:

XOR

public static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
{
    int hash = 0;
    foreach (T element in source)
    {
        hash = hash ^ EqualityComparer<T>.Default.GetHashCode(element);
    }
    return hash;
}

Un inconveniente de esto es que el hash para {"x", "x"} es el mismo que el hash para { "y", "y" }. Si eso no es un problema para su situación, sin embargo, es probablemente la solución más simple.

Adición

public static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
{
    int hash = 0;
    foreach (T element in source)
    {
        hash = unchecked (hash + 
            EqualityComparer<T>.Default.GetHashCode(element));
    }
    return hash;
}

El desbordamiento está bien aquí, de ahí el contexto explícito unchecked.

Todavía hay algunos casos desagradables (por ejemplo, {1, -1} y {2, -2}, pero es más probable que esté bien, particularmente con cadenas. En el caso de las listas que pueden contener tales números enteros, siempre se podría implementar una función de hash personalizada (tal vez uno que toma el índice de recurrencia de la valor específico como parámetro y devuelve un código hash único en consecuencia).

Aquí está un ejemplo de tal algoritmo que consigue alrededor del problema mencionado de una manera bastante eficiente. También tiene el beneficio de aumentar enormemente la distribución de los códigos hash generados (ver el artículo vinculado al final para alguna explicación). Un análisis matemático / estadístico de exactamente cómo este algoritmo produce "mejores" códigos hash sería bastante avanzado, pero probarlo a través de un una amplia gama de valores de entrada y el trazado de los resultados debe verificarlo lo suficientemente bien.

public static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
{
    int hash = 0;
    int curHash;
    int bitOffset = 0;
    // Stores number of occurences so far of each value.
    var valueCounts = new Dictionary<T, int>();

    foreach (T element in source)
    {
        curHash = EqualityComparer<T>.Default.GetHashCode(element);
        if (valueCounts.TryGetValue(element, out bitOffset))
            valueCounts[element] = bitOffset + 1;
        else
            valueCounts.Add(element, bitOffset);

        // The current hash code is shifted (with wrapping) one bit
        // further left on each successive recurrence of a certain
        // value to widen the distribution.
        // 37 is an arbitrary low prime number that helps the
        // algorithm to smooth out the distribution.
        hash = unchecked(hash + ((curHash << bitOffset) |
            (curHash >> (32 - bitOffset))) * 37);
    }

    return hash;
}

Multiplicación

Que tiene pocos beneficios si sobre la suma: números pequeños y una mezcla de números positivos y negativos pueden conducir a una mejor distribución de bits de hash. Como un negativo para compensar este " 1 " se convierte en una entrada inútil que no aporta nada y cualquier elemento cero resulta en un cero. Usted puede caso especial cero no causar este defecto importante.

public static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
{
    int hash = 17;
    foreach (T element in source)
    {
        int h = EqualityComparer<T>.Default.GetHashCode(element);
        if (h != 0)
            hash = unchecked (hash * h);
    }
    return hash;
}

Orden primero

El otro enfoque principal es hacer cumplir un orden primero, luego usar cualquier función de combinación de hash que desee. El orden en sí mismo es inmaterial siempre y cuando sea consistente.

public static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
{
    int hash = 0;
    foreach (T element in source.OrderBy(x => x, Comparer<T>.Default))
    {
        // f is any function/code you like returning int
        hash = f(hash, element);
    }
    return hash;
}

Esto tiene algunos beneficios significativos en que las operaciones de combinación posibles en f pueden tener propiedades de hash significativamente mejores (distribución de bits, por ejemplo), pero esto tiene un costo significativamente mayor. El orden es O(n log n) y la copia requerida de la colección es una memoria asignación que no se puede evitar dado el deseo de evitar modificar el original. GetHashCode normalmente, las implementaciones deben evitar por completo las asignaciones. Una posible implementación de f sería similar a la dada en el último ejemplo en la sección de Adición (por ejemplo, cualquier número constante de cambios de bits a la izquierda seguido de una multiplicación por un primo - incluso podría usar primos sucesivos en cada iteración sin costo adicional, ya que solo necesitan ser generados una vez).

Dicho esto, si fueras tratar con casos en los que se puede calcular y almacenar en caché el hash y amortizar el costo en muchas llamadas a GetHashCode este enfoque puede producir un comportamiento superior. También este último enfoque es aún más flexible, ya que puede evitar la necesidad de usar GetHashCode en los elementos si conoce su tipo y en su lugar usar operaciones por byte en ellos para obtener una distribución hash aún mejor. Es probable que ese enfoque sólo sea útil en los casos en que se determine que el desempeño es significativo. embotellamiento.

Finalmente, si quieres una visión general razonablemente completa y bastante no matemática del tema de los códigos hash y su efectividad en general, estas entradas de blog valdría la pena leer, en particular la Implementación de un algoritmo hash simple (pt II) post.

 72
Author: ShuggyCoUk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-08-20 15:30:07

Una alternativa a ordenar las listas de cadenas sería obtener los códigos hash de las cadenas y luego ordenar los códigos hash. (Comparar ints es menos costoso que comparar cadenas.) A continuación, puede utilizar un algoritmo para combinar los códigos hash que (con suerte) da una mejor distribución.

Ejemplo:

GetHashCodeOfList<T>(IEnumerable<T> list) {
   List<int> codes = new List<int>();
   foreach (T item in list) {
      codes.Add(item.GetHashCode());
   }
   codes.Sort();
   int hash = 0;
   foreach (int code in codes) {
      unchecked {
         hash *= 251; // multiply by a prime number
         hash += code; // add next hash code
      }
   }
   return hash;
}
 18
Author: Guffa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-15 15:57:23
    Dim list1 As ArrayList = New ArrayList()
    list1.Add("0")
    list1.Add("String1")
    list1.Add("String2")
    list1.Add("String3")
    list1.Add("abcdefghijklmnopqrstuvwxyz")

    Dim list2 As ArrayList = New ArrayList()
    list2.Add("0")
    list2.Add("String3")
    list2.Add("abcdefghijklmnopqrstuvwxyz")
    list2.Add("String2")
    list2.Add("String1")
    If GetHashCodeOfList(list1) = GetHashCodeOfList(list2) Then
        Stop
    Else
        Stop
    End If
    For x As Integer = list1.Count - 1 To 0 Step -1
        list1.RemoveAt(list1.Count - 1)
        list2.RemoveAt(list2.Count - 1)
        Debug.WriteLine(GetHashCodeOfList(list1).ToString)
        Debug.WriteLine(GetHashCodeOfList(list2).ToString)
        If list1.Count = 2 Then Stop
    Next


Private Function GetHashCodeOfList(ByVal aList As ArrayList) As UInt32
    Const mask As UInt16 = 32767, hashPrime As Integer = Integer.MaxValue
    Dim retval As UInt32
    Dim ch() As Char = New Char() {}
    For idx As Integer = 0 To aList.Count - 1
        ch = DirectCast(aList(idx), String).ToCharArray
        For idCH As Integer = 0 To ch.Length - 1
            retval = (retval And mask) + (Convert.ToUInt16(ch(idCH)) And mask)
        Next
    Next
    If retval > 0 Then retval = Convert.ToUInt32(hashPrime \ retval) 'Else ????
    Return retval
End Function
 0
Author: dbasnett,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-22 13:50:03