web-dev-qa-db-ja.com

VBAハッシュ文字列

Excel VBAを使用して長い文字列の短いハッシュを取得する方法

与えられたもの

  • 入力文字列は80文字以下です
  • 有効な入力文字は、[0..9] [A_Z]です。 _ /
  • 有効な出力文字は[0..9] [A_Z] [a_z](小文字と大文字を使用できます)
  • 出力ハッシュは12文字以下にする必要があります(短い方が良いです)
  • ハッシュが長すぎるため、一意である必要はありません。

これまでに行ったこと

私は this SO answer は4桁の16進コード(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 Pastebinからのテスト文字列 をコピーできます。
新しいExcelブックの列Aに貼り付けて、上記のコードを実行します。

Q:短いパーセンテージの重複を取得するのに十分な長さ(12文字)と十分な長さの文字列ハッシュを取得するにはどうすればよいですか。

16
nixda

文字列を3つの短い文字列に分割します(3で割り切れない場合、最後の文字列は他の2つの文字列よりも長くなります)。それぞれに対して「短い」アルゴリズムを実行し、結果を連結します。

私はコードを書くことができましたが、質問の質に基づいて、ここから取得できると思います!

編集:そのアドバイスでは不十分であることが判明しました。元のCRC16コードに重大な欠陥があります-つまり、次の行です。

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

これは16進値として解釈できるテキストのみを処理します。小文字と大文字は同じであり、アルファベットの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_で十分であることを示唆しています。

原則として、4文字の16進数は64kの一意の値を持つ必要があります。そのため、同じハッシュを持つ2つのランダムな文字列の可能性は64kに1つになります。 400個の文字列がある場合、400 x 399/2の「可能性のある衝突ペア」〜8万回の機会があります(非常にランダムな文字列があったと想定)。したがって、サンプルデータセットで3つの衝突を観察することは、不合理なスコアではありません。文字列Nの数が増えると、衝突の確率はNの2乗になります。hash12に32ビットの情報が追加されているため、N> 20 M程度の場合に衝突が発生すると予想されます(ハンドウェービング、インマイヘッド数学)。

明らかにhash12コードをもう少しコンパクトにすることができます-そして、それを任意の長さに拡張する方法を見るのは簡単なはずです。

最後にもう1つ。 RCアドレス指定を有効にしている場合、スプレッドシートの数式として=CRC16("string")を使用すると、追跡が困難な_#REF_エラーが発生します...そのため、名前を_hash4_に変更しました

13
Floris

多分他の人はこれが便利だと思うでしょう。

VBAで文字列の短いハッシュを生成するいくつかの異なる関数を収集しました。
私はコードを信用せず、すべてのソースが参照されています。

enter image description here

  1. CRC16
    • 関数:=CRC16HASH(A1) with this Code
    • ハッシュは4文字の長さのHEX文字列です
    • 19コード行
    • 4桁の長さのハッシュ= 6895行で624の衝突= 9%の衝突率
  2. CRC16数値
    • 関数:=CRC16NUMERIC(A1) with this Code
    • ハッシュは5桁の数字です
    • 92コード行
    • 5桁のハッシュ= 6895行で616回の衝突= 8.9%の衝突率
  3. CRC16 2回
    • 関数:=CRC16TWICE(A1) with this Code
    • ハッシュは8文字のHEX文字列です
    • ハッシュを12/16/20などの文字に拡張して、衝突率をさらに削減できます。
    • 39コード行
    • 8桁の長いハッシュ= 6895行で18の衝突= 0.23%の衝突率
  4. SHA1
    • 関数:=SHA1TRUNC(A1) with this Code
    • ハッシュは40文字の16進数文字列です
    • 142コード行
    • 切り捨てることができます
    • 4桁のハッシュ= 6895行で726回の衝突= 10.5%の衝突率
    • 5桁のハッシュ= 6895行で51回の衝突= 0.73%の衝突率
    • 6桁のハッシュ= 6895行で0の衝突= 0%の衝突率
  5. SHA1 + Base64
    • 関数:=BASE64SHA1(A1) with this Code
    • ハッシュは28文字の長さのUnicode文字列です(大文字と小文字を区別+特殊文字)
    • 41コード行
    • ライブラリ「Microsoft MSXML」を使用するため、.NETが必要
    • 切り捨てることができます
    • 4桁のハッシュ= 6895行で36回の衝突= 0.5%の衝突率
    • 5桁のハッシュ= 6895行で0の衝突= 0%の衝突率

ここ は、すべてのサンプル関数と多数のテスト文字列を含むテストワークブックです。

独自の機能を自由に追加してください。

32
nixda

衝突のレベルが低い文字列の32ビットハッシュ関数:

Public Function StrHash(text As String) As Long
    Dim i As Long
    StrHash = &H65D5BAAA

    For i = 1 To Len(text)
        StrHash = ((StrHash + AscW(Mid$(text, i, 1))) Mod 69208103) * 31&
    Next
End Function

または64ビットのハッシュ関数として:

Public Function StrHash64(text As String) As String
    Dim i&, h1&, h2&, c&
    h1 = &H65D5BAAA
    h2 = &H2454A5ED

    For i = 1 To Len(text)
        c = AscW(Mid$(text, i, 1))
        h1 = ((h1 + c) Mod 69208103) * 31&
        h2 = ((h2 + c) Mod 65009701) * 33&
    Next

    StrHash64 = Right("00000000" & Hex(h1), 8) & Right("00000000" & Hex(h2), 8)
End Function

FNVハッシュアルゴリズムに基づいています

4
Florent B.

以下はハッシュ関数ではありませんが、小さなリスト(検査で確認できるほど小さい)で衝突率が低い数値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))
0
Assad Ebrahim