最大30文字の長さの文字列をハッシュします。時間が問題である場合、これを行うための最良のアイデアは何でしょうか。関数は1億回以上呼び出されます。現在、私は次のコードを使用しています、
static UInt64 CalculateHash(string read, bool lowTolerance)
{
UInt64 hashedValue = 0;
int i = 0;
while (i < read.Length)
{
hashedValue += read.ElementAt(i) * (UInt64)Math.Pow(31, i);
if (lowTolerance) i += 2;
else i++;
}
return hashedValue;
}
static UInt64 CalculateHash(string read)
{
UInt64 hashedValue = 3074457345618258791ul;
for(int i=0; i<read.Length; i++)
{
hashedValue += read[i];
hashedValue *= 3074457345618258799ul;
}
return hashedValue;
}
これはKnuthハッシュです。 Jenkins を使用することもできます。
まず、GetHashCode()
の使用を検討してください。
既存の実装の簡単な改善:
static UInt64 CalculateHash(string read, bool lowTolerance)
{
UInt64 hashedValue = 0;
int i = 0;
ulong multiplier = 1;
while (i < read.Length)
{
hashedValue += read[i] * multiplier;
multiplier *= 37;
if (lowTolerance) i += 2;
else i++;
}
return hashedValue;
}
高価な浮動小数点計算、およびElementAt
のオーバーヘッドを回避します。
ところで(UInt64)Math.Pow(31, i)
は、長い文字列ではうまく機能しません。浮動小数点の丸めにより、15を超える文字の乗数は0になります。
私はPaul Hsiehの実装を試しましたが、衝突はほとんどありません(とにかく私のシナリオでは)
実装を高速化するには、(UInt64)Math.Pow(31, i)
呼び出しをルックアップに置き換える必要があります。31
の最初の30の累乗のテーブルを事前計算し、実行時に使用します。長さの制限は30なので、必要な要素は31だけです。
private static unsigned long[] Pow31 = new unsigned long[31];
static HashCalc() {
Pow31[0] = 1;
for (int i = 1 ; i != Pow31.Length ; i++) {
Pow31[i] = 31*Pow31[i-1];
}
}
// In your hash function...
hashedValue += read.ElementAt(i) * Pow31[i];