VBA哈希字符串

时间:2013-02-05 21:45:00

标签: vba hash excel-2003

如何使用Excel VBA获取长字符串的短哈希

给出了什么

  • 输入字符串不超过80个字符
  • 有效输入字符为:[0..9] [A_Z]。 _ /
  • 有效输出字符为[0..9] [A_Z] [a_z] (可以使用大小写)
  • 输出哈希值不应超过~12个字符(更短更好)
  • 根本不需要是唯一的,因为这会导致散列太长

到目前为止我做了什么

我认为this SO answer是一个好的开始,因为它会生成一个4位十六进制代码(CRC16)。

但是4位数字很少。在我的测试中,有400个字符串,20%在其他地方得到了副本  产生碰撞的机会太高了。

Sub tester()
    For i = 2 To 433
        Cells(i, 2) = CRC16(Cells(i, 1))
    Next i
End Sub


Function CRC16(txt As String)
Dim x As Long
Dim mask, i, j, nC, Crc As Integer
Dim c As String

Crc = &HFFFF

For nC = 1 To Len(txt)
    j = Val("&H" + Mid(txt, nC, 2))
    Crc = Crc Xor j
    For j = 1 To 8
        mask = 0
        If Crc / 2 <> Int(Crc / 2) Then mask = &HA001
        Crc = Int(Crc / 2) And &H7FFF: Crc = Crc Xor mask
    Next j
Next nC

CRC16 = Hex$(Crc)
End Function

如何重现

您可以复制这400张test strings from pastebin 将它们粘贴到新Excel工作簿中的A列,然后执行上面的代码。

问:如何获得足够短(12个字符)的字符串哈希,并且足够长以获得一小部分重复项。

4 个答案:

答案 0 :(得分:27)

也许其他人会觉得这很有用。

我已经收集了一些不同的函数来在VBA中生成字符串的短哈希 我不赞成代码,所有来源都被引用。

enter image description here

  1. CRC16
    • 功能:=CRC16HASH(A1)使用此Code
    • hash是一个4个字符长的HEX字符串
    • 19个代码行
    • 4位长哈希= 6895行中的624次冲突= 9%碰撞率
  2. CRC16 numeric
    • 功能:=CRC16NUMERIC(A1)使用此Code
    • hash是一个5位数的长号
    • 92行代码
    • 5位长哈希= 6895行中的616次冲突= 8.9%碰撞率
  3. CRC16 twice
    • 功能:=CRC16TWICE(A1)使用此Code
    • hash是一个8个字符长的HEX字符串
    • 哈希可以扩展到12/16/20等字符以进一步降低碰撞率
    • 39个代码行
    • 8位长散列= 6895行中的18次碰撞= 0.23%碰撞率
  4. SHA1
    • 功能:=SHA1TRUNC(A1)使用此Code
    • hash是一个40个字符长的HEX字符串
    • 142行代码
    • 可以截断
    • 4位数哈希= 6895行中的726次冲突= 10.5%碰撞率
    • 5位数哈希= 6895行中的51次碰撞= 0.73%碰撞率
    • 6位数哈希= 6895行中的0次冲突= 0%碰撞率
  5. SHA1 + Base64
    • 功能:=BASE64SHA1(A1)使用此Code
    • hash是一个28个字符长的unicode字符串(区分大小写+特殊字符)
    • 41个代码行
    • 需要.NET,因为它使用库“Microsoft MSXML”
    • 可以截断
    • 4位数哈希= 6895行中的36次冲突= 0.5%碰撞率
    • 5位数哈希= 6895行中的0次冲突= 0%碰撞率
  6. Here是我的测试工作簿,包含所有示例函数和大量测试字符串。

    随意添加自己的功能。

答案 1 :(得分:13)

将您的字符串拆分为三个较短的字符串(如果不能被三整除,则最后一个字符串将长于另外两个字符串)。对每个算法运行“短”算法,并连接结果。

我可以编写代码,但根据问题的质量,我认为你可以从这里获取它!

编辑:事实证明这个建议还不够。您的原始CRC16代码存在严重缺陷 - 即:

j = Val("&H" + Mid(txt, nC, 2))

这只处理可以解释为十六进制值的文本:小写和大写字母相同,并且忽略字母表中F之后的任何内容(据我所知)。任何好的东西都是奇迹。如果用

替换该行
j = asc(mid(txt, nC, 1))

事情变得更好 - 每个ASCII代码至少都将生命视为自己的价值。

将此更改与我之前提出的提案相结合,您将获得以下代码:

Function hash12(s As String)
' create a 12 character hash from string s

Dim l As Integer, l3 As Integer
Dim s1 As String, s2 As String, s3 As String

l = Len(s)
l3 = Int(l / 3)
s1 = Mid(s, 1, l3)      ' first part
s2 = Mid(s, l3 + 1, l3) ' middle part
s3 = Mid(s, 2 * l3 + 1) ' the rest of the string...

hash12 = hash4(s1) + hash4(s2) + hash4(s3)

End Function

Function hash4(txt)
' copied from the example
Dim x As Long
Dim mask, i, j, nC, crc As Integer
Dim c As String

crc = &HFFFF

For nC = 1 To Len(txt)
    j = Asc(Mid(txt, nC)) ' <<<<<<< new line of code - makes all the difference
    ' instead of j = Val("&H" + Mid(txt, nC, 2))
    crc = crc Xor j
    For j = 1 To 8
        mask = 0
        If crc / 2 <> Int(crc / 2) Then mask = &HA001
        crc = Int(crc / 2) And &H7FFF: crc = crc Xor mask
    Next j
Next nC

c = Hex$(crc)

' <<<<< new section: make sure returned string is always 4 characters long >>>>>
' pad to always have length 4:
While Len(c) < 4
  c = "0" & c
Wend

hash4 = c

End Function

您可以将此代码放在电子表格中=hash12("A2")等。为了好玩,您还可以使用“新的,改进的”hash4算法,并查看它们的比较方式。我创建了一个数据透视表来计算碰撞 - hash12算法没有,而hash4只有3。我相信你可以从中找出如何创建hash8,...您的问题中的“无需独特”表明,您可能只需要“改进”hash4

原则上,四字符十六进制应具有64k唯一值 - 因此具有相同散列的两个随机字符串在64k中的概率为1。当你有400个字符串时,有400 x 399/2“可能的碰撞对”~80k个机会(假设你有高度随机的字符串)。因此,在样本数据集中观察三次碰撞不是不合理的分数。随着你的字符串数N上升,碰撞的概率变为N的平方。在hash12中有额外的32位信息,当N> 1时,你会看到碰撞。 20 M左右(handwaving,in-my-head-math)。

显然,你可以使hash12代码更紧凑 - 而且应该很容易看到如何将它扩展到任何长度。

哦 - 还有最后一件事。如果您启用了RC寻址,则使用=CRC16("string")作为电子表格公式会产生难以追踪的#REF错误...这就是我重命名hash4

的原因

答案 2 :(得分:2)

对于记录,这个记录快速生成具有低级别冲突的32位哈希:

Public Function HashFNV(txt As String) As Long
  Const max# = 2 ^ 31
  Dim hash#, upper&, i&
  If txt = Empty Then Exit Function
  hash = &H11C9DC5
  For i = 1 To Len(txt)
    hash = 31# * (hash - upper * max Xor AscW(Mid$(txt, i, 1)))
    upper = hash / max
  Next
  HashFNV = hash - upper * max Or &H80000000 * (upper And 1&)
End Function

答案 3 :(得分:0)

虽然以下不是哈希函数,但我已经将它用作生成数字ID的快速方法,这些数字ID在小列表上具有较低的冲突率(小到足以通过检查验证)。

工作原理:A列保存第2行以后的字符串。在第1行中,A1和B1在字符串的中间位置保持任意的开始和结束位置。公式使用字符串的第一个字母和从中间字符串中取出的固定字母,并使用LEN()作为“扇形函数”来减少碰撞的机会。

 =CODE(A2)*LEN(A2) + CODE(MID(A2,$A$1,$B$1))*LEN(MID(A2,$A$1,$B$1))

如果从具有固定宽度字段的数据库表中提取字符串,则可能需要修剪长度:

 =CODE(TRIM(C8))*LEN(TRIM(C8))
       +CODE(MID(TRIM(C8),$A$1,1))*LEN(MID(TRIM(C8),$A$1,$B$1))