web-dev-qa-db-fra.com

Chaîne de hachage VBA

Comment obtenir un hachage court d'une longue chaîne avec Excel VBA

Ce qui est donné

  • La chaîne d'entrée ne dépasse pas 80 caractères
  • Les caractères d'entrée valides sont: [0..9] [A_Z].
  • Les caractères de sortie valides sont [0..9] [A_Z] [a_z] (les majuscules et les minuscules peuvent être utilisées)}
  • Le hachage en sortie ne doit pas dépasser environ 12 caractères (plus court est encore mieux)
  • Pas besoin d'être unique du tout car cela entraînera un hachage trop long

_/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.

14
nixda

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.

13
Floris

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.

enter image description here

  1. CRC16
    • Fonction: =CRC16HASH(A1) with this Code
    • le hachage est une chaîne HEX de 4 caractères 
    • 19 lignes de code
    • Hash long à 4 chiffres = 624 collisions sur 6895 lignes = taux de collision de 9%
  2. CRC16 numérique
    • Fonction: =CRC16NUMERIC(A1) with this Code
    • le hachage est un nombre long de 5 chiffres
    • 92 lignes de code
    • Hachage de 5 chiffres = 616 collisions sur 6895 lignes = taux de collision de 8,9%
  3. CRC16 deux fois
    • Fonction: =CRC16TWICE(A1) with this Code
    • le hachage est une chaîne HEX de 8 caractères
    • hachage peut être étendu à 12/16/20 etc. caractères pour réduire le taux de collision encore plus
    • 39 lignes de code
    • Hash long de 8 chiffres = 18 collisions sur 6895 lignes = taux de collision de 0,23%
  4. SHA1
    • Fonction: =SHA1TRUNC(A1) with this Code
    • le hachage est une chaîne HEX de 40 caractères
    • 142 lignes de code
    • peut être tronqué
    • Dièse à 4 chiffres = 726 collisions sur 6895 lignes = taux de collision de 10,5%
    • Dièse à 5 chiffres = 51 collisions sur 6895 lignes = taux de collision de 0,73%
    • Dièse à 6 chiffres = 0 collisions sur 6895 lignes = taux de collision de 0%
  5. SHA1 + Base64
    • Fonction: =BASE64SHA1(A1) with this Code
    • le hachage est une chaîne unicode de 28 caractères (sensible à la casse + caractères spéciaux)
    • 41 lignes de code
    • nécessite .NET puisqu'il utilise la bibliothèque "Microsoft MSXML"
    • peut être tronqué
    • Dièse à 4 chiffres = 36 collisions sur 6895 lignes = taux de collision de 0,5%
    • Dièse à 5 chiffres = 0 collisions sur 6895 lignes = taux de collision de 0%

Ici 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.

26
nixda

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
2
Florent B.

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))
0
Assad Ebrahim