【问题标题】:Simple string hashing function简单的字符串散列函数
【发布时间】:2011-04-11 01:48:29
【问题描述】:

我正在尝试将字符串散列为整数,以便将其放入数组中。但是我对散列函数不太了解,这就是为什么我目前的方法只是将字符的所有 ASCII 数字加在一起,然后将其修改为数组大小。

有没有更快/更好的简单方法?

【问题讨论】:

  • 你在做一个哈希表吗?
  • half of that 的副本。
  • 我现在用的是Delphi
  • Hal,正如你所看到的,哈希值很难理解,并且经常成为书呆子战斗的借口。
  • 有没有关于哈希函数的社区维基?如果没有,那么从输入类型、性能和语言实现结构化的信息开始可能是有意义的。

标签: string delphi hash


【解决方案1】:

FNV-1a hash 快速且易于实施。

【讨论】:

  • 它快速、简单且糟糕。它的雪崩特性是不可接受的。如果您必须使用 FNV 变体,FNV-1a 在这方面要好得多。或者,更好的是,根本不使用 FNV。有很多更好的选择,这个是浪费时间。
  • +1。 FNV 维基百科条目击败了詹金斯之一。并且与语言无关。
  • @Dummy:只是因为他们忽略了雪崩结果。事实证明,FNV-1a 并不糟糕,但 FNV 确实如此。就此而言,Jenkins 的 lookup3 还可以,而他的一次一个则很糟糕。
  • @Greg:我怀疑你比我更关心你的代表分数,但如果你编辑你的答案以指定 FNV-1a,那么我很高兴删除反对票。我仍然不喜欢这个 FNV 变种,但至少它并不可怕。
  • Comparing FNV-1, FNV-1a, DJB2, DJB2a, SDBM, CRC32。其中 FNV-1a 是最好的。
【解决方案2】:

请参阅http://www.strchr.com/hash_functions 以获得非常好的散列函数面板。

在Delphi实现中,有几个版本:

首先想到的是官方IniFiles.pas单位TStringHash.HashOf方法中使用的那个。包括更快的 asm 版本:

function HashOf(P: PByteArray; Len: integer): cardinal;
// algorithm from IniFiles.TStringHash.HashOf
{$ifdef PUREPASCAL}
var I: Integer;
begin
  Result := 0;
  for I := 1 to Len do
    Result := ((Result shl 2) or (Result shr (SizeOf(Result)*8-2))) xor P[I];
end;
{$else}
asm // faster asm version by Synopse
    or edx,edx
    jz @z
    push ebx
    mov ebx,edx     // ebx = length(Key)
    mov edx,eax     // edx = Text
    xor eax,eax     // eax = Result
    xor ecx,ecx     // ecx = Result shl 2 = 0
@1: shr eax,$1e     // eax = Result shr (SizeOf(Result) * 8 - 2))
    or ecx,eax      // ecx = ((Result shl 2) or (Result shr (SizeOf(Result)*8-2)))
    movzx eax,byte ptr [edx] // eax = ord(Key[i])
    inc edx
    xor eax,ecx     // eax = () xor ord(Key[i])
    dec ebx
    lea ecx,[eax*4] // ecx = Result shl 2
    jnz @1
    pop ebx
@z:
end;
{$endif}

“C 编程语言”第 3 版中的经典 Kernighan & Ritchie 哈希 - 不是最好的,但简单而高效的代码。

function kr32(crc: cardinal; buf: PAnsiChar; len: cardinal): cardinal;
var i: integer;
begin
  for i := 0 to len-1 do
    crc := ord(buf[i])+crc*31;
  result := crc;
end;

zlib 中实现的快速“Adler”CRC - 优化 asm 版本here

function Adler32Pas(Adler: cardinal; p: pointer; Count: Integer): cardinal;
var s1, s2: cardinal;
    i, n: integer;
begin
  s1 := LongRec(Adler).Lo;
  s2 := LongRec(Adler).Hi;
  while Count>0 do begin
    if Count<5552 then
      n := Count else
      n := 5552;
    for i := 1 to n do begin
      inc(s1,pByte(p)^);
      inc(cardinal(p));
      inc(s2,s1);
    end;
    s1 := s1 mod 65521;
    s2 := s2 mod 65521;
    dec(Count,n);
  end;
  result := word(s1)+cardinal(word(s2)) shl 16;
end;

我自己的更快变体 - 不是可重入的,但更快,因为它会被 DWORD 读取 - 甚至更快的asm version here

function Hash32(Data: pointer; Len: integer): cardinal;
function SubHash(P: PCardinalArray; L: integer): cardinal;
{$ifdef HASINLINE}inline;{$endif}
var s1,s2: cardinal;
    i: PtrInt;
const Mask: array[0..3] of cardinal = (0,$ff,$ffff,$ffffff);
begin
  if P<>nil then begin
    s1 := 0;
    s2 := 0;
    for i := 1 to L shr 4 do begin // 16 bytes (4 DWORD) by loop - aligned read
      inc(s1,P^[0]);
      inc(s2,s1);
      inc(s1,P^[1]);
      inc(s2,s1);
      inc(s1,P^[2]);
      inc(s2,s1);
      inc(s1,P^[3]);
      inc(s2,s1);
      inc(PtrUInt(P),16);
    end;
    for i := 1 to (L shr 2)and 3 do begin // 4 bytes (DWORD) by loop
      inc(s1,P^[0]);
      inc(s2,s1);
      inc(PtrUInt(P),4);
    end;
    inc(s1,P^[0] and Mask[L and 3]);      // remaining 0..3 bytes
    inc(s2,s1);
    result := s1 xor (s2 shl 16);
  end else
    result := 0;
end;
begin // use a sub function for better code generation under Delphi
  result := SubHash(Data,Len);
end;

经典的CRC32版本-你可以找一个很optimized asm version (using 8 tables) here

function UpdateCrc32(aCRC32: cardinal; inBuf: pointer; inLen: integer) : cardinal;
var i: integer;
begin
  result := aCRC32;
  // if we used a dynamic table, we assume we want shorter code size
  for i := 1 to inLen do begin
    result := crc32Tab[byte(result xor pByte(inBuf)^)] xor (result shr 8);
    inc(cardinal(inBuf));
  end;
end;

【讨论】:

【解决方案3】:

正如 Dummy00001 所指出的那样,这已经被问过并回答过。看看Best algorithm for hashing number values?,特别是使用MurmurHash的建议。

我推荐 MurmurHash 因为:

  1. 非常快。

  2. 它的分布和雪崩特性非常适合非加密哈希。

  3. 它在最坏情况下的表现还是不错的。

我用过。一点都不烂。

编辑

https://forums.embarcadero.com/thread.jspa?threadID=13902&tstart=0 上有很多关于如何最好地将其移植到 Delphi 的讨论。生成的代码可在 https://forums.codegear.com/thread.jspa?threadID=14879

获得

德尔福翻译

function Murmur2(const S: AnsiString; const Seed: LongWord=$9747b28c): LongWord;
var
    h: LongWord;
    len: LongWord;
    k: LongWord;
    data: Integer;
const
    // 'm' and 'r' are mixing constants generated offline.
    // They're not really 'magic', they just happen to work well.
    m = $5bd1e995;
    r = 24;
begin
    len := Length(S);

    //The default seed, $9747b28c, is from the original C library

    // Initialize the hash to a 'random' value
    h := seed xor len;

    // Mix 4 bytes at a time into the hash
    data := 1;

    while(len >= 4) do
    begin
        k := PLongWord(@S[data])^;

        k := k*m;
        k := k xor (k shr r);
        k := k* m;

        h := h*m;
        h := h xor k;

        data := data+4;
        len := len-4;
    end;

    {   Handle the last few bytes of the input array
            S: ... $69 $18 $2f
    }
    Assert(len <= 3);
    if len = 3 then
        h := h xor (LongWord(s[data+2]) shl 16);
    if len >= 2 then
        h := h xor (LongWord(s[data+1]) shl 8);
    if len >= 1 then
    begin
        h := h xor (LongWord(s[data]));
        h := h * m;
    end;

    // Do a few final mixes of the hash to ensure the last few
    // bytes are well-incorporated.
    h := h xor (h shr 13);
    h := h * m;
    h := h xor (h shr 15);

    Result := h;
end;

通过所有self-tests from the original C implementation

【讨论】:

  • 有任何指针可以证明散列函数不会遭受您在其他地方抱怨的雪崩效应吗? Murmur 使用几乎相同的算法,只是略有不同。
  • 我在其他地方回答过这个问题,但我会在这里重复一遍:sites.google.com/site/murmurhash/avalanche
  • 有些人可能会认为,既然来源是MurmurHash的规范站点,那肯定是可疑的。这些人应该更仔细阅读,并点击链接到home.comcast.net/~bretm/hash
  • @Steven:关于链接 - 尝试查看链接中的图表时出现 404 错误。您也没有提供任何指向广告算法示例实现的链接。附言第二个链接 (~bretm) 显示了 Jenkins 的不同雪崩图,结论也很好:“Bob Jenkins 的这个哈希函数应该适合通用用途,无论是哈希表查找、基本文件指纹识别还是其他非加密用途。”您是非常值得信赖的信息来源;)
  • @Dummy:注意:Mulvey 的图表是用于查找3,而不是一次一个。
【解决方案4】:

Jenkins hash function 应该可以帮助您入门。

我目前的方法是将所有字符的 ASCII 数字相加,然后将其修改为数组大小。

您丢弃了重要的信息,即字符串中字符的位置。这是个坏主意,因为这样字符串“AB”和“BA”将具有相同的哈希值。

可以使用hash = hash*P1 + str[i]*P2 + P3; 之类的表达式,而不是简单的加法,保持原始状态,其中 Pi 是一些素数。如果我需要快速散列函数,我就是这样做的。我经常使用 7、5 和 3 作为质数,但数字应该明显调整(以及 hash 的初始值),以便哈希函数的结果可用于您的任务。

更多信息请阅读the corresponding (and rather informative) Wikipedia article

【讨论】:

  • 当然,从那里开始,然后继续前进。正如您链接到的文章所示,它具有非常可怕的雪崩特征。看看所有的黄色像素。
  • @Steven:那里的黄点真的很少。而且绝对比简单的加法要好。它速度快,在现实世界的弦上工作得很好。我在几个地方使用它,它确实改善了密钥分配。
  • 我不否认它比加法更好——我什至在对已删除答案的评论中指出了 AB/BA 问题。但是,没有理由应该有 any 个黄色像素。像 memcachedb 这样的项目使用 Murmur 是有充分理由的!
  • 别傻了。总会有黄色像素 - 因为仍然必须对散列函数的结果应用模数,这会丢弃更多有用的信息位。杂音与 Jenkins 或 FNV 并没有太大的不同。或者你甚至没有费心去检查 Murmur 是如何工作的?我认为你在这里过于迂腐了。
  • 哦,我肯定迂腐,但我也完全正确。它并没有根本不同,但它旨在避免这个特殊缺陷。要进行良好的视觉比较,请查看sites.google.com/site/murmurhash/avalanche
【解决方案5】:

我尝试了很多快速哈希函数并选择了这个:

function StrHash(const st:string):cardinal; 
 var
  i:integer;
 begin
  result:=0;
  for i:=1 to length(st) do
   result:=result*$20844 xor byte(st[i]);
 end;

它与 K&R 函数一样快(实际上更快),但分布更好(更均匀)。

【讨论】:

    【解决方案6】:

    一个非常简单的方法是对所有值进行异或。据我所知最简单的。

    【讨论】:

    • 天啊。 a xor a = 0。所以任何具有偶数个相同字母的字符串... Anagrams 任何人?
    猜你喜欢
    • 1970-01-01
    • 2013-11-16
    • 2011-02-07
    • 1970-01-01
    • 2011-12-22
    • 1970-01-01
    • 2017-06-23
    • 2015-09-07
    • 1970-01-01
    相关资源
    最近更新 更多