ハッシュを一定数のバケットに均一に投影する方法


11

こんにちは仲間の統計学者、

ハッシュを生成するソース(たとえば、タイムスタンプとその他の情報を含む文字列を計算し、md5を使用してハッシュ化)があり、それを固定数のバケット(たとえば100)に投影したいと考えています。

サンプルハッシュ:0fb916f0b174c66fd35ef078d861a367

最初はハッシュの最初の文字のみを使用してバケットを選択することだと思っていましたが、これは非常に不均一な投影になります(つまり、一部の文字が非常にまれに表示され、他の文字が非常に頻繁に表示される)。

次に、このヘキサ文字列をchar値の合計を使用して整数に変換し、モジュロを使ってバケットを選択しようとしました。

import sys

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

それは実際に機能しているようですが、これがなぜ、どの程度本当であるかを説明できる常識や理論上の結果があるかどうかはわかりません。

[編集]少し考えてから、次の結論に達しました。理論的には、ハッシュを数値として解釈することにより、(非常に大きな)整数に変換できます:i = h [0] + 16 * h [1] + 16 * 16 * h [2] ... + 16 ^ 31 * h [31](各文字は16進数を表します)。次に、この大きな数をモジュロしてバケットスペースに投影します。[/編集]

よろしくお願いします!


3
実際のハッシュは、そのような不均一な結果を与えるべきではありません。ハッシュアルゴリズムが正しく実装されていますか?
whuber

ハッシュアルゴリズム自体にバグがあるとは思えません。しかし、16進数ダイジェストの文字は厳密に均一でなく、独立して分布しているのではないかと思います。
oDDsKooL 2012

1
それが私が疑わしいと思うことです。MD5のような「暗号的に安全な」ハッシュは、入力の分布について非常に特別なものがない限り(「特別な」とは、MD5アルゴリズムと密接に関連していることを意味します)、すべての桁の均一な分布が必要です。あなたが提案する解決策は、ハッシュを再ハッシュすることになりますが、それはまったく必要ありません。
whuber

1
Md5ハッシュの最初の文字は均一でなければなりません。ただし、16の値(16進数のエンコーディング)しか取得できません
leonbloy

1
その点を主張してくれてありがとう、私はハッシュの最初の文字を数え直したところ、確かに〜均一に分散されているようです:{'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}。したがって、この16状態のランダムジェネレーターを100状態の空間に射影する必要があるので、私の質問は多かれ少なかれ答えられます。ハッシュの最初の2文字を使用して、範囲[0,16+ 16 * 16]そしてそれを100にモジュロします。
oDDsKooL 2012

回答:


13

注:ディスカッションから浮かび上がった答えをコメントにして、関心のある人が読みやすくする

(更新版)

B

主な手順は次のとおりです。

  1. 各イベントハッシュするei2N
  2. R×[0,1[p=i2N
  3. bibiBp<bi+1B

1.の一般的な解決策は、MurmurHashを使用して64ビットまたは128ビットの整数を生成することです。

j=1..Bp[bjB,bj+1B[

(python)擬似コードでは、全体的な手順は次のようになります。

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

(以前のバージョン、本当に最適ではありません)

最初の観察は、ハッシュのn番目の文字はアルファベットに関して均一に分散されるべきであるということです(ここでは16文字です-これを指摘してくれた@leonbloyのおかげです)。

次に、それを[0,100 [の範囲に射影するための秘訣は、ハッシュから2文字(たとえば、1番目と2番目の位置)を取得し、それを使って整数を生成することです。

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

この値は[0,16+(16-1)* 16 [の範囲内にあるため、[0、100 [の範囲内にバケットを生成するには、それを100にモジュロする必要 があります。コメントで指摘されているように、最初の文字は2番目の文字よりも影響力があるため、分布の均一性に影響を与えます。

bucket = int_value % 100

理論的には、数値として解釈することにより、ハッシュ全体を(非常に大きな)整数に変換できます:i = h [0] + 16 * h [1] + 16 * 16 * h [2] ... + 16 ^ 31 * h [31](各文字は16進数を表します)。次に、この大きな数をモジュロしてバケットスペースに投影します。次に、iのモジュロを取ることは、分配的および加法的演算に分解できることに注意できます。

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

この回答の改善は大歓迎です。
oDDsKooL 2013

「任意の2文字」が「均一に分散」されている場合、からバケットは通常からバケットよりも50%多くのヒットをバケットごとに取得するため、これは良い解決策のようには見えません。実際には、ハッシュ自体を100バケットにハッシュするために、恐ろしいハッシュ関数を使用しています。その目的で、既知の優れたハッシュ関数を使用しないのはなぜですか?55 56 990555699
whuber

同意する。より良い手巻きの解決策は、16ビット空間の整数に変換できる16進数文字列のチャンクを取得することです。次に、実際の値を最大16ビットの整数値で割り、100を掛けて丸めます。
spdrnl

2nn2

@whuberこれは非常に最適ではなく、連続した[0,1 [間隔に投影する方がはるかに優れています。実験的にも確認しました。その見解を反映するように答えを編集します。
oDDsKooL 2015年

0

私は同様の問題を抱えており、より速くより簡単に任意の言語で実装できる別の解決策を思いつきました。

私の最初の考えは、アイテムを一定数のバケットで迅速かつ均一にディスパッチすること、そしてスケーラブルであるためにはランダム性を模倣することでした。

そのため、文字列(または実際には任意の種類のデータ)を指定すると、浮動小数点数を返すこの小さな関数を[0、1 [にコーディングしました。

ここ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)

もちろん、それはランダムではなく、実際には疑似ランダムでもありません。同じデータは常に同じチェックサムを返します。しかし、それはランダムに動作し、かなり高速です。

各アイテムをバケット番号math.floor(N * pseudo_random_checksum(item))に割り当てるだけで、簡単にディスパッチして後でNバケットのアイテムを取得できます。


サンプルが[0,1]に均一に配置されるという直感または証拠はありますか?
sud_

@sud_この関数は、ここで議論される:stackoverflow.com/a/19303725/1608467は
fbparis

@sud_また、正当な乱数ジェネレータと比較するためにいくつかのテストを実行しましたが、テストしたすべてのケースで問題ありませんでした。
fbparis
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.