使用する
この特定のケース用のヒューリスティック関数を開発しました。[0,9!-1]
マッピングは 間ではなく間であるため、完全なハッシュではありません[1,767359]
が、 ですO(1)
。
767359 ビットが 0 に設定されたファイル / 予約メモリ / が既にあると仮定しましょう (例: mem = [False] * 767359
)。8puzzle パターンを python 文字列 (例: '125346987'
) にマッピングします。次に、ハッシュ関数は次のように決定されます。
def getPosition( input_str ):
data = []
opts = range(1,10)
n = int(input_str[0])
opts.pop(opts.index(n))
for c in input_str[1:len(input_str)-1]:
k = opts.index(int(c))
opts.pop(k)
data.append(k)
ind = data[3]<<14 | data[5]<<12 | data[2]<<9 | data[1]<<6 | data[0]<<3 | data[4]<<1 | data[6]<<0
output_str = str(ind)+str(n)
output = int(output_str)
return output
つまり、8puzzle パターン =125346987
が既に使用されているかどうかを確認するには、次のようにする必要があります。
pattern = '125346987'
pos = getPosition(pattern)
used = mem[pos-1] #mem starts in 0, getPosition in 1.
完全なハッシングを行うと、9 個必要になります。ブール値を格納するビット。この場合、さらに 2 倍 ( 767359/9! = 2.11
) が必要ですが、1Mb にも満たないことを思い出してください (かろうじて100KB )。
この関数は簡単に元に戻せることに注意してください。
小切手
これが機能する理由と、衝突が発生しない理由を数学的に証明できますが、これはプログラミング フォーラムなので、考えられるすべての順列に対して実行し、すべてのハッシュ値 (位置) が実際に異なることを確認してみましょう。
def getPosition( input_str ):
data = []
opts = range(1,10)
n = int(input_str[0])
opts.pop(opts.index(n))
for c in input_str[1:len(input_str)-1]:
k = opts.index(int(c))
opts.pop(k)
data.append(k)
ind = data[3]<<14 | data[5]<<12 | data[2]<<9 | data[1]<<6 | data[0]<<3 | data[4]<<1 | data[6]<<0
output_str = str(ind)+str(n)
output = int(output_str)
return output
#CHECKING PURPOSES
def addperm(x,l):
return [ l[0:i] + [x] + l[i:] for i in range(len(l)+1) ]
def perm(l):
if len(l) == 0:
return [[]]
return [x for y in perm(l[1:]) for x in addperm(l[0],y) ]
#We generate all the permutations
all_perms = perm([ i for i in range(1,10)])
print "Number of all possible perms.: "+str(len(all_perms)) #indeed 9! = 362880
#We execute our hash function over all the perms and store the output.
all_positions = [];
for permutation in all_perms:
perm_string = ''.join(map(str,permutation))
all_positions.append(getPosition(perm_string))
#We wan't to check if there has been any collision, i.e., if there
#is one position that is repeated at least twice.
print "Number of different hashes: "+str(len(set(all_positions)))
#also 9!, so the hash works properly.
それはどのように機能しますか?
この背後にある考え方は、ツリーと関係があります。最初は、それぞれが数字に対応する 9 つのノードに向かう 9 つの分岐があります。これらのノードのそれぞれから、8 つのノードに向かう 8 つのブランチがあり、それぞれが親を除く数字に対応し、次に 7 というようになります。
まず、入力文字列の最初の数字を別の変数に格納し、「ノード」リストからポップアウトします。これは、最初の数字に対応する分岐を既に取っているためです。
次に、8 つのブランチがあり、2 番目の桁に対応するブランチを選択します。8 つの分岐があるため、選択した分岐のインデックスを格納するには 3 ビットが必要であり、最大値111
は 8 番目の分岐のものであることに注意してください (分岐 1 ~ 8 を binary にマップします000-111
)。ブランチ インデックスを選択して保存したら、その値を取り出して、次のノード リストにこの数字が再び含まれないようにします。
分岐 7、6、および 5 についても同様に処理を進めます。最大値は になりますが、7 つの分岐がある場合でも 3 ビットが必要であることに注意してください110
。5 つのブランチがある場合、インデックスはせいぜい binary になり100
ます。
次に、4 つのブランチに到達します。これは、3 つのブランチの場合と同じように、2 ビットだけで格納できることに気付きます。2 つの分岐の場合は 1 ビットだけ必要で、最後の分岐の場合はビットは必要ありません。最後の桁を指す分岐が 1 つだけあり、元の 1-9 リストの残りになります。
これまでのところ、分離された変数に格納されている最初の数字と、ブランチを表す 7 つのインデックスのリストがあります。最初の 4 つのインデックスは 3 ビットで表現でき、次の 2 つのインデックスは 2 ビットで表現でき、最後のインデックスは 1 ビットで表現できます。
アイデアは、このすべてのインデックスをビット形式で連結して、より大きな数を作成することです。17 ビットなので、この数は最大で2^17=131072
. ここで、格納した最初の数字をその数字の末尾に追加するだけです (最大でこの数字は 9 になります)。作成できる最大の数字は です1310729
。
しかし、もっとうまくやることができます: 最大値は binary でしたが、5 つの分岐があるときは 3 ビットが必要だったことを思い出してください100
。0 の数が多いビットが最初になるようにビットを配置するとどうなるでしょうか。その場合、最悪のシナリオでは、最終的なビット番号は次の連結になります。
100 10 101 110 111 11 1
10 進数で は76735
です。次に、前と同じように処理を進め (末尾に 9 を追加)、可能な最大の生成数は767359
であり、これは必要なビットの量であり、入力 string987654321
に対応し、最小の可能な数は1
入力 string に対応します123456789
。
最後に、なぜ最初の数字を別の変数に格納して最後に追加したのか疑問に思うかもしれません。その理由は、それを保持していた場合、最初の分岐の数は 9 であったため、最初のインデックス (1-9) を格納するために 4 ビット ( 0000
~1000
) が必要だったからです。その場合、可能な最大数 (したがって、必要なメモリ量) は次のようになります。
1000 100 10 101 110 111 11 1
これは 10 進数で 1125311 です (1.13Mb 対 768Kb)。1.13M/0.768K = 1.47
10 進数の値 ( ) を加算するだけの場合と比較して、比率が 4 ビットの比率と関係があることを確認するのは非常に興味深いことです2^4/10 = 1.6
(違いは、最初のアプローチでは4 ビットを完全には使用していません)。