web-dev-qa-db-ja.com

整数ハッシュキーを受け入れる整数ハッシュ関数は何ですか?

整数ハッシュキーを受け入れる整数ハッシュ関数は何ですか?

89
Lear

クヌースの乗法:

hash(i)=i*2654435761 mod 2^32

一般的に、ハッシュサイズの順に乗数を選択する必要があります(2^32にあります)、共通の要因はありません。このようにして、ハッシュ関数はすべてのハッシュ空間を均一にカバーします。

編集:このハッシュ関数の最大の欠点は、それが分割可能性を保持することです。したがって、整数がすべて2または4(これは珍しいことではありません)で割り切れる場合、ハッシュも大きくなります。これはハッシュテーブルの問題です-使用されているバケットの1/2または1/4だけになる可能性があります。

39
Rafał Dowgird

次のアルゴリズムが非常に良好な統計分布を提供することがわかりました。各入力ビットは、約50%の確率で各出力ビットに影響します。衝突はありません(各入力は異なる出力になります)。 CPUに組み込みの整数乗算ユニットがない場合を除き、アルゴリズムは高速です。 intが32ビットであると仮定したCコード(Javaの場合、>>>>>で置き換え、unsignedを削除します):

unsigned int hash(unsigned int x) {
    x = ((x >> 16) ^ x) * 0x45d9f3b;
    x = ((x >> 16) ^ x) * 0x45d9f3b;
    x = (x >> 16) ^ x;
    return x;
}

マジックナンバーは、 特別なマルチスレッドテストプログラム を使用して計算され、それは何時間も実行され、雪崩効果(単一の入力ビットが変化した場合に変化する出力ビットの数変更、平均でほぼ16である必要があります)、出力ビット変更の独立性(出力ビットは互いに依存しない)、および入力ビットが変更された場合の各出力ビットの変更の確率。計算された値は、 MurmurHash で使用される32ビットのファイナライザよりも優れており、 [〜#〜]を使用する場合とほぼ同じ(完全ではありません) aes [〜#〜] 。わずかな利点は、同じ定数が2回使用されることです(前回テストしたときに少し速くなりましたが、まだそうなっているかどうかはわかりません)。

0x45d9f3b0x119de1f3multiplicative inverse )に置き換えると、プロセスを逆にする(ハッシュから入力値を取得する)ことができます。

unsigned int unhash(unsigned int x) {
    x = ((x >> 16) ^ x) * 0x119de1f3;
    x = ((x >> 16) ^ x) * 0x119de1f3;
    x = (x >> 16) ^ x;
    return x;
}

64ビットの数値の場合、次のものを使用することをお勧めします。最速ではない場合もあります。これは splitmix64 に基づいており、ブログ記事 Better Bit Mixing (mix 13)に基づいているようです。

uint64_t hash(uint64_t x) {
    x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
    x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
    x = x ^ (x >> 31);
    return x;
}

Javaの場合は、longを使用し、Lを定数に追加し、>>>>>に置き換え、unsignedを削除します。この場合、反転はより複雑です。

uint64_t unhash(uint64_t x) {
    x = (x ^ (x >> 31) ^ (x >> 62)) * UINT64_C(0x319642b2d24d8ec3);
    x = (x ^ (x >> 27) ^ (x >> 54)) * UINT64_C(0x96de1b173f119089);
    x = x ^ (x >> 30) ^ (x >> 60);
    return x;
}

更新: Hash Function Prospector プロジェクトもご覧ください。他の(おそらくはより良い)定数がリストされています。

126
Thomas Mueller

データの分散方法に依存します。単純なカウンターの場合、最も単純な関数

f(i) = i

良いでしょう(最適だと思いますが、それを証明することはできません)。

26
erikkallen

このページ は、一般的にまともな傾向があるいくつかの単純なハッシュ関数をリストしますが、単純なハッシュには、うまく機能しない病理学的なケースがあります。

7
Tyler McHenry
  • 32ビットの乗法(非常に高速)@rafalを参照

    #define hash32(x) ((x)*2654435761)
    #define H_BITS 24 // Hashtable size
    #define H_SHIFT (32-H_BITS)
    unsigned hashtab[1<<H_BITS]  
    .... 
    unsigned slot = hash32(x) >> H_SHIFT
    
  • 32ビットおよび64ビット(良好な分布): MurmurHash

  • 整数ハッシュ関数
5
bill

Eternally Confuzzled にいくつかのハッシュアルゴリズムの概要があります。 Bob Jenkinsの一度に1つずつのハッシュをお勧めします。これはすぐに雪崩に達するため、効率的なハッシュテーブルルックアップに使用できます。

3
Christoph

答えは次のような多くのものに依存します。

  • どこで採用するつもりですか?
  • ハッシュで何をしようとしていますか?
  • 暗号学的に安全なハッシュ関数が必要ですか?

Merkle-Damgard SHA-1などのハッシュ関数のファミリーをご覧になることをお勧めします

2
dirkgently

事前にデータを知らなくても、ハッシュ関数は「良い」とは言えないと思います。そして、あなたはそれで何をしようとしているのか分からずに。

未知のデータサイズのハッシュテーブルよりも優れたデータ構造があります(ここでハッシュテーブルのハッシュを行っていると仮定しています)。限られた量のメモリに格納する必要がある「有限」数の要素があることがわかっている場合、私は個人的にハッシュテーブルを使用します。ハッシュ関数について考え始める前に、自分のデータについて簡単な統計分析を行い、データがどのように分布しているかなどを試してみます。

1
Ouanixi

高速で優れたハッシュ関数は、いくつかの高速順列をより低い品質で組み合わせることで構成できます。

  • 不均一な整数との乗算
  • バイナリ回転
  • xorshift

乱数生成の [〜#〜] pcg [〜#〜] で示されるように、優れた品質のハッシュ関数を生成します。

これは実際には、rrxmrrxmsx_0レシピとmurmurハッシュが、故意または無意識のうちに使用しているレシピでもあります。

個人的に見つけた

uint64_t rol(const uint64_t& n,int i){
  return (n<<i)|(n>>(64-i);
}
uint64_t hash(const uint64_t& n){
  uint64_t c = random_uneven_64_bit_integer_constant"; 
  return c*rol(c*n,32);
}

十分に良い。

または、 GHash のようなガロア体の乗算を使用できます。これらは、最新のCPUで適度に高速になり、ワンステップで優れた品質を実現します。

0
Lykos

ランダムハッシュ値については、一部のエンジニアは黄金比の素数(2654435761)が悪い選択であると言いました。私のテスト結果では、それは真実ではないことがわかりました。代わりに、2654435761はハッシュ値をかなり適切に配布します。

#define MCR_HashTableSize 2^10

unsigned int
Hash_UInt_GRPrimeNumber(unsigned int key)
{
  key = key*2654435761 & (MCR_HashTableSize - 1)
  return key;
}

ハッシュテーブルのサイズは2の累乗でなければなりません。

整数の多くのハッシュ関数を評価するためのテストプログラムを作成しましたが、結果はGRPrimeNumberが非常に良い選択であることを示しています。

私が試してみました:

  1. total_data_entry_number/total_bucket_number = 2、3、4;ここで、total_bucket_number =ハッシュテーブルサイズ。
  2. ハッシュ値ドメインをバケットインデックスドメインにマップします。つまり、Hash_UInt_GRPrimeNumber()に示すように、論理値と(hash_table_size-1)を使用してハッシュ値をバケットインデックスに変換します。
  3. 各バケットの衝突数を計算します。
  4. マッピングされていないバケット、つまり空のバケットを記録します。
  5. すべてのバケットの最大衝突数を見つけます。つまり、チェーンの最長の長さ。

私のテスト結果では、ゴールデンレシオの素数には常に空のバケットが少ないか、空のバケットがゼロで、コリジョンチェーンの長さが最短であることがわかりました。

整数の一部のハッシュ関数は適切であると主張されていますが、テスト結果では、total_data_entry/total_bucket_number = 3の場合、最長チェーン長は10(最大衝突数> 10)より大きく、多くのバケットはマップされません(空のバケット) )、これは、空のバケットがゼロで、チェーンの長さが最長の3の結果をゴールデンレシオのプライムナンバーハッシングで比較した場合と比較すると非常に悪い結果です。

ところで、私のテスト結果では、shifting-xorハッシュ関数の1つのバージョンがかなり良いことがわかりました(mikeraで共有されています)。

unsigned int Hash_UInt_M3(unsigned int key)
{
  key ^= (key << 13);
  key ^= (key >> 17);    
  key ^= (key << 5); 
  return key;
}
0
Chen-ChungChia

このスレッドを見つけて以来、私はsplitmix64(Thomas Muellerの answer で示されています)を使用しています。しかし、私は最近、Pelle Evensenの rrxmrrxmsx_ に偶然出会いました。これは、元のMurmurHash3ファイナライザーとその後継(splitmix64およびその他のミックス)よりも非常に優れた統計分布をもたらしました。 Cのコードスニペットを次に示します。

#include <stdint.h>

static inline uint64_t ror64(uint64_t v, int r) {
    return (v >> r) | (v << (64 - r));
}

uint64_t rrxmrrxmsx_0(uint64_t v) {
    v ^= ror64(v, 25) ^ ror64(v, 50);
    v *= 0xA24BAED4963EE407UL;
    v ^= ror64(v, 24) ^ ror64(v, 49);
    v *= 0x9FB21C651E98DF25UL;
    return v ^ v >> 28;
}

また、PelleはMurmurHash3の最終ステップで使用される64ビットミキサーの 詳細な分析 およびより新しいバリアントを提供します。

0