68 votes

L'obtention de hachage d'une liste de chaînes, quel que soit l'ordre

Je voudrais écrire une fonction GetHashCodeOfList() qui retourne un hash-code d'une liste de chaînes, quel que soit l'ordre. 2 listes avec les mêmes chaînes doit renvoyer le même 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.

J'ai eu quelques pensées:

  1. Je peux tout d'abord trier la liste, puis de les combiner la liste triée en 1 longue chaîne, puis en appel en GetHashCode(). Cependant, le tri est une opération lente.

  2. Je peux obtenir le hash de chaque corde (en appelant string.GetHashCode()) dans la liste, puis en multipliant tous les hachages et de l'appel de Mod UInt32.MaxValue. Par exemple: "String1".GetHashCode() * "String2".GetHashCode * … MOD UInt32.MaxValue. Mais cela entraîne un certain nombre de dépassement.

Quelqu'un aurait-il des idées?

Merci d'avance pour votre aide.

82voto

Jon Skeet Points 692016

Il existe différentes approches de la ici la en deux catégories principales, chacune avec généralement leurs propres avantages et inconvénients, en termes d'efficacité et de performance. Il est probablement préférable de choisir la plus simple de l'algorithme pour quelle application et d'utiliser uniquement la plus complexe des variantes, si nécessaire, quelle que soit la situation.

Notez que ces exemples utilisent EqualityComparer<T>.Default depuis qui traitera des éléments null proprement. Vous pourriez faire mieux que zéro pour la valeur null si désiré. Si T est contraint de struct il est également inutile. Vous pouvez hisser EqualityComparer<T>.Default de recherche de la fonction si vous le souhaitez.

Commutative Opérations

Si vous utiliser des opérations sur les hashcodes des entrées individuelles qui sont commutative alors cela conduira au même résultat quel que soit l'ordre.

Il y a plusieurs évident options sur les nombres:

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 inconvénient de cette est que le hachage { "x", "x" } est le même que le hachage { "y", "y" }. Si ce n'est pas un problème pour votre situation de bien, c'est probablement la solution la plus simple.

Plus

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;
}

Le dépassement est très bien ici, d'où l'explicite unchecked contexte.

Il y a encore quelques méchants cas (par exemple, {1, -1} et {2, -2}, mais il est plus susceptible d'être d'accord, en particulier avec des chaînes. Dans le cas de listes susceptibles de contenir de tels entiers, vous pouvez toujours mettre en œuvre une coutume fonction de hachage (peut-être un qui prend l'indice de la récidive de la valeur en paramètre et retourne un unique code de hachage en conséquence).

Voici un exemple d'un tel algorithme qui permet de contourner le problème susmentionné d'une manière très efficace. Il a également l'avantage d'augmenter fortement la distribution des codes de hachage généré (voir l'article lié à la fin de l'explication). Mathématique/statistique de l'analyse de exactement comment cet algorithme produit "au mieux" des codes de hachage serait assez avancé, mais de le tester sur une large gamme de valeurs d'entrée et de tracer les résultats devraient vérifier assez 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;
}

La Multiplication

Qui a peu d'avantages sur l'addition: un petit nombre et un mélange de nombres positifs et négatifs, ils peuvent conduire à une meilleure distribution de hachage bits. Comme un point négatif pour compenser ce "1" devient inutile entrée en contribuant rien et tout zéro de l'élément de résultats par un zéro. Vous pouvez le cas à zéro non pour provoquer ce défaut majeur.

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;
}

D'abord

L'autre approche de base est d'appliquer certains de commande d'abord, puis utilisez l'une de hachage la fonction de combinaison vous le souhaitez. La commande elle-même est sans importance tant que c'est cohérent.

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;
}

Cela a des avantages importants en ce que la combinaison des opérations possibles en f peut avoir nettement mieux de hachage (propriétés de la distribution de bits par exemple) mais cela se fait au coût nettement plus élevé. Le tri est O(n log n) et la copie de la collection est une allocation de mémoire que vous ne pouvez pas éviter que le désir d'éviter de modifier l'original. GetHashCode des implémentations devraient normalement éviter les allocations entièrement. Une implémentation possible de l' f serait similaire à celui donné dans le dernier exemple, en vertu de la section (par exemple, un nombre constant de bits quarts à gauche, puis par une multiplication par un premier - vous pouvez même utiliser des nombres premiers successifs à chaque itération, sans frais supplémentaires, ceux-ci doivent uniquement être généré à la fois).

Cela dit, si nous avions affaire à des cas où l'on pouvait calculer et de mettre en cache les valeurs de hachage et d'amortir le coût sur de nombreux appels d' GetHashCode cette approche peut donner supérieure de comportement. Aussi cette dernière approche est d'autant plus flexible car il permet d'éviter la nécessité d'utiliser l' GetHashCode sur les éléments si il sait leur type et au lieu d'utiliser par octet d'opérations pour un rendement encore meilleur de hachage de la distribution. Une telle approche serait probablement de l'utiliser uniquement dans le cas où la performance a été identifié comme étant un goulot d'étranglement important.

Enfin, si vous voulez une assez complet et plutôt non-mathématique aperçu des codes de hachage et de leur efficacité en général, ces messages de blog serait utile de lit, en particulier la mise en Œuvre d'un simple algorithme de hachage (pt II) de la poste.

24voto

Guffa Points 308133

Une alternative à trier les listes de chaînes serait d'obtenir les codes de hachage des cordes et puis trier les codes de hachage. (Comparaison des ints est moins cher que de comparer des chaînes de caractères.) Vous pouvez ensuite utiliser un algorithme de fusionner les codes de hachage (j'espère) donne une meilleure distribution.

Exemple:

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;
}

0voto

dbasnett Points 4114
    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

0voto

SultanBaby Points 182

Partage de ma propre solution simple, en plus de la déjà existant grande réponse et d'autres suggestions:

Donnée:

ArrayList list1 = new ArrayList(), list2 = new ArrayList();

Premier tri:

list1.Sort(); list2.Sort();

En affirmant:

String.Join("-", list1).GetHashCode() = String.Join("-", list2).GetHashCode();

Devrait donner des true si le contenu de ces listes sont différentes. Ce n'est pas une mauvaise solution si votre liste n'est jamais grand.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X