Comment projeter uniformément un hachage sur un nombre fixe de compartiments


11

Salut chers statisticiens,

J'ai une source générant des hachages (par exemple, calculer une chaîne avec un horodatage et d'autres informations et hacher avec md5) et je veux la projeter dans un nombre fixe de compartiments (disons 100).

exemple de hachage: 0fb916f0b174c66fd35ef078d861a367

Ce que je pensais au début était de n'utiliser que le premier caractère du hachage pour choisir un seau, mais cela conduit à une projection extrêmement non uniforme (c'est-à-dire que certaines lettres apparaissent très rarement et d'autres très fréquemment)

Ensuite, j'ai essayé de convertir cette chaîne hexa en un entier en utilisant la somme des valeurs char, puis j'ai pris le modulo pour choisir un compartiment:

import sys

for line in sys.stdin:
    i = 0
    for c in line:
        i += ord(c)
    print i%100

Cela semble fonctionner dans la pratique, mais je ne sais pas s'il y a du bon sens ou des résultats théoriques qui pourraient expliquer pourquoi et dans quelle mesure cela est vrai?

[Modifier] Après réflexion, je suis arrivé à la conclusion suivante: En théorie, vous pouvez convertir le hachage en un (très grand) entier en l'interprétant comme un nombre: i = h [0] + 16 * h [1] + 16 * 16 * h [2] ... + 16 ^ 31 * h [31] (chaque lettre représente un nombre hexadécimal). Ensuite, vous pouvez moduler ce grand nombre pour le projeter dans l'espace du compartiment. [/Éditer]

Merci !


3
Un vrai hachage ne devrait pas donner de tels résultats non uniformes. Êtes-vous sûr que l'algorithme de hachage est correctement implémenté?
whuber

Je doute qu'il y ait un bug dans l'algorithme de hachage lui-même. Mais je soupçonne que les caractères du résumé hexadécimal ne sont pas strictement uniformes et distribués indépendamment.
oDDsKooL

1
C'est ce que je trouve douteux: un hachage "cryptographiquement sécurisé" comme MD5 devrait avoir des distributions uniformes de tous les chiffres, sauf s'il y a quelque chose de très spécial dans la distribution de l'entrée ("spécial" signifie intimement lié à l'algorithme MD5). La solution que vous proposez revient à ré-hacher le hachage, ce qui ne devrait pas du tout être nécessaire.
whuber

1
Le premier caractère du hachage Md5 doit être uniforme. Mais vous n'obtiendrez que 16 valeurs (c'est un encodage hexadécimal)
leonbloy

1
Merci d'avoir insisté sur ce point, j'ai relancé mon comptage sur la première lettre des hachages et il semble en effet ~ uniformément réparti: {'a': 789, 'c': 769, 'b': 755, 'e': 730, «d»: 804, «f»: 749, «1»: 716, «0»: 758, «3»: 734, «2»: 735, «5»: 787, «4»: 756, «7»: 771, «6»: 721, «9»: 764, «8»: 765}. Par conséquent, ma question est plus ou moins répondue car j'ai juste besoin de projeter ce générateur aléatoire de 16 états sur un espace de 100 états, ce qui peut être fait en utilisant les 2 premières lettres du hachage pour générer un entier de plage [0,16+ 16 * 16] et modulez-le à 100. Cela vous dérange si je réponds à ma propre question;)?
oDDsKooL

Réponses:


13

NB: mettre en forme la réponse issue de la discussion dans les commentaires afin qu'elle soit plus facile à lire pour les personnes intéressées

(Version mise à jour)

Supposons que nous ayons une source générant des événements indépendants que nous voulons distribuer uniformément dans compartimentsB

Les étapes clés sont les suivantes:

  1. hacher chaque événement e à un entier i de taille 2N
  2. projeter sur R×[0,1[ comme p=i2N
  3. trouver le seau correspondant bi pour que biBp<bi+1B

Pour 1. une solution populaire consiste à utiliser MurmurHash pour générer un entier 64 ou 128 bits.

Pour 3. une solution simple est d'itérer sur j=1..B et vérifiez que p est dans [bjB,bj+1B[

En pseudo-code (python), la procédure globale pourrait être:

def hash_to_bucket(e, B):
    i = murmurhash3.to_long128(str(e))
    p = i / float(2**128)
    for j in range(0, B):
        if j/float(B) <= p and (j+1)/float(B) > p:
            return j+1
    return B

(version précédente, vraiment pas optimale)

La première observation est que la n lettre -ème du hachage doit être réparti uniformément par rapport à l'alphabet (qui est ici 16 longues lettres - grâce à @leonbloy pour le souligner).

Ensuite, pour le projeter sur une plage de [0,100 [, l'astuce consiste à prendre 2 lettres du hachage (par exemple 1ère et 2ème positions) et générer un entier avec cela:

int_value = int(hash[0])+16*int(hash[1])

Cette vie de valeur dans l'intervalle [0,16+ (16-1) * 16 [, donc nous avons juste à modulo à 100 pour générer un seau dans [0, 100 [Plage: Comme indiqué dans les commentaires, faire impact sur l'uniformité de la distribution puisque la première lettre est plus influente que la seconde.

bucket = int_value % 100

En théorie, vous pouvez convertir le hachage entier en un (très grand) entier en l'interprétant comme un nombre: i = h [0] + 16 * h [1] + 16 * 16 * h [2] ... + 16 ^ 31 * h [31] (chaque lettre représente un nombre hexadécimal). Ensuite, vous pouvez moduler ce grand nombre pour le projeter dans l'espace du compartiment. On peut alors noter que la prise du modulo de i peut être décomposée en une opération distributive et additive:

imodN=((h0modN)+(16modN×h1modN)+...+(1631modN×h31modN))modN

Toute amélioration de cette réponse est la bienvenue.
oDDsKooL

Cela ne semble pas être une bonne solution car lorsque «deux lettres» sont «uniformément réparties», les compartiments de à obtiennent généralement 50% plus de hits par compartiment que les compartiments de à . En effet, vous utilisez une terrible fonction de hachage pour tenter de hacher le hachage lui-même en 100 seaux. Pourquoi ne pas simplement utiliser une bonne fonction de hachage connue à cette fin? 0555699
whuber

Je suis d'accord. Une meilleure solution roulée à la main serait de prendre un morceau de la chaîne hexadécimale qui pourrait se traduire par exemple par un entier de 16 bits. Divisez ensuite la valeur réelle par la valeur entière maximale de 16 bits, multipliez par cent et arrondissez.
spdrnl

Si vous utilisez plusieurs compartiments sous forme de 2n, vous ne pouvez prendre que le dernier nbits du hachage (et son équivalent en caractères hexadécimaux). De cette façon, le résultat de l'opération modulo sera exactement le même que lors du calcul sur la conversion complète en entier. Cela peut également fonctionner correctement si vous utilisez un nombre de seaux qui n'est pas une puissance de2.
alesc

@whuber Je suis d'accord que ce n'est pas tout à fait optimal et projeter sur un intervalle continu [0,1 [est beaucoup mieux. J'ai vérifié cela aussi expérimentalement. Je vais modifier la réponse pour refléter ce point de vue.
oDDsKooL

0

J'ai eu un problème similaire et j'ai trouvé une solution différente qui peut être plus rapide et plus facilement implémentée dans n'importe quelle langue.

Ma première pensée a été d'envoyer des articles rapidement et uniformément dans un nombre fixe de seaux, et aussi pour être évolutif, je devrais imiter le hasard.

J'ai donc codé cette petite fonction renvoyant un nombre flottant dans [0, 1 [étant donné une chaîne (ou tout type de données en fait).

Ici en Python:

import math
def pseudo_random_checksum(s, precision=10000):
    x = sum([ord(c) * math.sin(i + 1) for i,c in enumerate(s)]) * precision
    return x - math.floor(x)

Bien sûr, ce n'est pas aléatoire, en fait ce n'est même pas pseudo aléatoire, les mêmes données renverront toujours la même somme de contrôle. Mais il agit comme aléatoire et c'est assez rapide.

Vous pouvez facilement répartir et récupérer ultérieurement des éléments dans N compartiments en affectant simplement chaque élément au numéro de compartiment math.floor (N * pseudo_random_checksum (item)).


Avez-vous une intuition ou une preuve qu'il placera les échantillons uniformément dans [0,1]?
sud_

@sud_ Cette fonction est discutée ici: stackoverflow.com/a/19303725/1608467
fbparis

@sud_ De plus, j'ai effectué des tests pour le comparer avec un générateur de nombres aléatoires légitime et c'était OK dans tous les cas que j'ai testés.
fbparis
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.