Comment obtenir un hachage court d'une longue chaîne avec Excel VBA
Ce qui est donné
_/Ce que j'ai fait jusqu'à présent
Je pensais que cette réponse SO est un bon début, car elle génère un code hexadécimal à 4 chiffres (CRC16).
Mais 4 chiffres étaient trop petits. Lors de mon test avec 400 chaînes, 20% ont obtenu un duplicata ailleurs.
La possibilité de générer une collision est trop élevée.
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
Comment reproduire
Vous pouvez copier ces chaînes de 400 tests à partir de Pastebin .
Collez-les à la colonne A dans un nouveau classeur Excel et exécutez le code ci-dessus.
Q: Comment obtenir un hachage de chaîne suffisamment court (12 caractères) et suffisamment long pour obtenir un faible pourcentage de doublons.
Divisez votre chaîne en trois chaînes plus courtes (si elles ne sont pas divisibles par trois, la dernière sera plus longue que les deux autres). Exécutez votre algorithme "court" sur chacun et concaténez les résultats.
Je pourrais écrire le code, mais compte tenu de la qualité de la question, je pense que vous pouvez le prendre ici!
EDIT: Il s'avère que ce conseil n'est pas suffisant. Votre code CRC16 d'origine contient une faille grave, à savoir la ligne qui dit:
j = Val("&H" + Mid(txt, nC, 2))
Cela ne gère que le texte pouvant être interprété comme une valeur hexadécimale: les lettres minuscules et majuscules sont identiques, et tout ce qui suit F dans l'alphabet est ignoré (pour autant que je sache). Que quelque chose de bon sorte du tout est un miracle. Si vous remplacez la ligne par
j = asc(mid(txt, nC, 1))
Les choses fonctionnent mieux - chaque code ASCII commence au moins la vie comme sa propre valeur.
En combinant ce changement avec la proposition que j'ai faite précédemment, vous obtenez le code suivant:
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
Vous pouvez placer ce code dans votre feuille de calcul sous la forme =hash12("A2")
etc. Pour vous amuser, vous pouvez également utiliser le nouvel algorithme hash4, amélioré, et voir comment ils se comparent. J'ai créé un tableau croisé dynamique pour compter les collisions - il n'y en avait pas pour l'algorithme hash12
et seulement 3 pour le hash4
. Je suis sûr que vous pouvez comprendre comment créer hash8
, ... à partir de ceci. Le "inutile d'être unique" de votre question suggère que le hash4
"amélioré" est peut-être tout ce dont vous avez besoin.
En principe, un hexagone de quatre caractères devrait avoir 64k valeurs uniques - ainsi, la probabilité que deux chaînes aléatoires aient le même hachage serait de 1 sur 64k. Lorsque vous avez 400 chaînes, il existe 400 x 399/2 "paires de collision possibles" ~ 80 000 opportunités (en supposant que vous disposiez de chaînes hautement aléatoires). Observer trois collisions dans l'ensemble de données de l'échantillon n'est donc pas un score déraisonnable. Au fur et à mesure que votre nombre de chaînes N augmente, la probabilité de collision se transforme en carré de N. Avec les 32 bits supplémentaires dans le hachage 12, vous vous attendez à voir des collisions lorsque N> 20 M environ mathématique).
Vous pouvez évidemment rendre le code hash12 un peu plus compact - et il devrait être facile de voir comment l'étendre à n'importe quelle longueur.
Oh, et une dernière chose. Si l'adressage RC est activé, l'utilisation de =CRC16("string")
en tant que formule de feuille de calcul donne une erreur difficile à suivre #REF
... c'est pourquoi je l'ai renommé hash4
.
Peut-être que d'autres trouveront cela utile.
J'ai rassemblé différentes fonctions pour générer un hachage court d'une chaîne dans VBA.
Je ne prends pas le crédit pour le code et toutes les sources sont référencées.
=CRC16HASH(A1)
with this Code=CRC16NUMERIC(A1)
with this Code=CRC16TWICE(A1)
with this Code=SHA1TRUNC(A1)
with this Code=BASE64SHA1(A1)
with this CodeIci est mon classeur de test avec tous les exemples de fonctions et un grand nombre de chaînes de test.
N'hésitez pas à ajouter vos propres fonctions.
Pour mémoire, celui-ci génère rapidement un hachage 32 bits avec un faible niveau de collision:
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
Bien que l’information ci-dessous ne soit pas une fonction de hachage, je l’ai utilisée comme moyen rapide de générer des identifiants numériques présentant un faible taux de collision sur une petite liste (suffisamment petit pour être vérifié par inspection).
Comment ça marche : La colonne A contient les chaînes à partir de la ligne 2. Dans la rangée 1, A1 et B1 tiennent une position de début et de fin arbitraire au milieu de la chaîne. La formule utilise la première lettre de la chaîne et une lettre fixe tirée de la chaîne moyenne et utilise LEN () comme "fonction de ventilation" pour réduire les risques de collision.
=CODE(A2)*LEN(A2) + CODE(MID(A2,$A$1,$B$1))*LEN(MID(A2,$A$1,$B$1))
Si des chaînes sont extraites d'une table de base de données avec des champs de largeur fixe, vous devrez peut-être couper les longueurs:
=CODE(TRIM(C8))*LEN(TRIM(C8))
+CODE(MID(TRIM(C8),$A$1,1))*LEN(MID(TRIM(C8),$A$1,$B$1))