web-dev-qa-db-fra.com

Bon gethashcode () remplacer la liste des objets foo sur la commande

EnumerableObject : IEnumerable<Foo>

wraps a List<Foo>

Si EnumerableObject a.SequenceEquals( EnumerableObject b), alors ils sont égaux.

Par conséquent, un GetHashCode doit être mis en œuvre. Le problème est de xoriser chaque élément de la liste renvoyera le même code de hachage pour n'importe quelle liste avec tous et uniquement les mêmes éléments, quel que soit leur ordre. C'est bien en termes de fonctionnement, mais de nombreuses collisions, qui ralentiront la récupération, etc.

Qu'est-ce qu'un bon et rapide GetHashCode méthode pour les listes d'objets dépendantes de l'ordre?

30
Ben B.

Je ferais de la même manière que je combine normalement des codes de hasch - avec une addition et une multiplication:

public override int GetHashCode()
{
    unchecked
    {
        int hash = 19;
        foreach (var foo in foos)
        {
            hash = hash * 31 + foo.GetHashCode();
        }
        return hash;
    }
}

(Notez que vous ne devez rien ajouter à la liste une fois que cela a été utilisé pour la clé dans une table de hachage de toute description, car le hachage changera. Cela suppose également qu'il n'y a pas d'entrées nuls - s'il pouvait y avoir besoin de prendre en compte cela.)

60
Jon Skeet

Tout d'abord, vérifiez que vous avez besoin d'un hashcode du tout. Allez-vous mettre ces listes dans une structure mappée de hachage (par exemple dictionnaire, hashset, etc.)? Sinon, oubliez-le.

Maintenant, en supposant que vous signifiez que l'énumérableObject remplace déjà Equals(object) (et, espérons-le donc, donc également implémente IEquatable<EnumerableObject>) Pour une raison quelconque, c'est bien nécessaire. Vous souhaitez équilibrer la vitesse de la vitesse par rapport au bit.

Un bon point de départ est un MUL + AJOUTER ou un SHIFT + XOR comme:

public override int GetHashCode()
{
    int res = 0x2D2816FE;
    foreach(var item in this)
    {
        res = res * 31 + (item == null ? 0 : item.GetHashCode());
    }
    return res;
}

(Cela suppose que vous utilisez l'élément.equals () pour votre comparaison sur l'égalité de séquence, si vous utilisez un équivalent d'ineéquitycomparer, vous devez appeler dans son HASHCODE).

De là, nous pouvons optimiser.

Si les articles NULL sont interdisés, retirez la chèque nulle (soyez prudent, cela rendra le code de code si cela trouve jamais une null).

Si de très grandes listes sont courantes, nous avons besoin de réduire le nombre examiné, tout en essayant de ne pas entraîner de nombreuses collisions. Comparez les différentes implémentations suivantes:

public override int GetHashCode()
{
    int res = 0x2D2816FE;
    int max = Math.Min(Count, 16);
    for(int i = 0, i != max; ++i)
    {
        var item = this[i];
        res = res * 31 + (item == null ? 0 : item.GetHashCode());
    }
    return res;
}

public override int GetHashCode()
{
    int res = 0x2D2816FE;
    int min = Math.Max(-1, Count - 16);
    for(int i = Count -1, i != min; --i)
    {
        var item = this[i];
        res = res * 31 + (item == null ? 0 : item.GetHashCode());
    }
    return res;
}

public override int GetHashCode()
{
    int res = 0x2D2816FE;
    int step = Count / 16 + 1;
    for(int i = 0, i < Count; i += step)
    {
        var item = this[i];
        res = res * 31 + (item == null ? 0 : item.GetHashCode());
    }
    return res;
}

Chacun d'entre eux restreint le nombre total d'éléments examinés, qui accélère l'exécution, mais risquent des haubans de qualité les plus pauvres. Qui (le cas échéant) dépend mieux de savoir si les collections ayant le même début ou la même fin sont plus probables.

Changer le nombre 16 ci-dessus ajuste l'équilibre; Plus petit est plus rapide mais plus élevé est une meilleure qualité de hachage avec un risque plus faible de collisions de hachage.

Edit: Et maintenant, vous pouvez utiliser mon implémentation de SpookyHash v. 2 :

public override int GetHashCode()
{
  var hasher = new SpookyHash();//use methods with seeds if you need to prevent HashDos
  foreach(var item in this)
    hasher.Update(item.GetHashCode());//or relevant feeds of item, etc.
  return hasher.Final().GetHashCode();
}

Cela créera une bien meilleure distribution que multine + Add ou Shift + Xor, tout en étant particulièrement rapide (en particulier dans les processus 64 bits que l'algorithme est optimisé pour cela, bien qu'il fonctionne bien sur 32 bits).

13
Jon Hanna

La méthode .GetHashCode() renvoie généralement un hachage basé sur la référence d'objet (adresse du pointeur). En effet, le calcul du code de hachage de chaque article dans une liste énumérable peut être très intensif. Au lieu d'écraser le comportement existant, je préfère utiliser une méthode d'extension et l'utiliser que lorsque le code de hachage doit être déterminé de manière déterministe:

public static class EnumerableExtensions
{
    public static int GetSequenceHashCode<TItem>(this IEnumerable<TItem> list)
    {
        if (list == null) return 0;
        const int seedValue = 0x2D2816FE;
        const int primeNumber = 397;
        return list.Aggregate(seedValue, (current, item) => (current * primeNumber) + (Equals(item, default(TItem)) ? 0 : item.GetHashCode()));
    }
}
4
MovGP0