如何使用Excel VBA获取长字符串的短哈希
给出了什么
到目前为止我做了什么
我认为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个字符)的字符串哈希,并且足够长以获得一小部分重复项。
答案 0 :(得分:27)
也许其他人会觉得这很有用。
我已经收集了一些不同的函数来在VBA中生成字符串的短哈希 我不赞成代码,所有来源都被引用。
=CRC16HASH(A1)
使用此Code =CRC16NUMERIC(A1)
使用此Code =CRC16TWICE(A1)
使用此Code =SHA1TRUNC(A1)
使用此Code =BASE64SHA1(A1)
使用此Code 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))