どこでも実際に使用せずにこれを実証する最良の方法dict
は、おそらく非常に単純で、 とは非常に異なり、dict
完全に役に立たないものを実装することです。bytes
fixed-sizeから same-fixed- size への固定サイズのマッピングのようにbytes
。dict
(これは、たとえばルーティング テーブルに使用できます。明らかに速度と柔軟性が犠牲になりますが、アンパックされたキーをアンパックされた値にマッピングするよりもはるかにコンパクトになります。)
ハッシュ テーブルは単なる(hash, key, value)
タプルの配列です。これの要点はデータを詰め込むことなので、それらを に詰め込みますstruct
。つまり、大きなbytearray
ものをストレージに使用できます。スロットを空としてマークするには、そのハッシュ値を — に設定します。これは、実数を に変換して0
「エスケープ」する必要があることを意味します。簡単にするために、可能な限り最も馬鹿げたアルゴリズムも使用します。0
1
probe
class FixedHashTable(object):
hashsize = 8
def __init__(self, elementsize, size):
self.elementsize = elementsize
self.size = size
self.entrysize = self.hashsize + self.elementsize * 2
self.format = 'q{}s{}s'.format(self.elementsize, self.elementsize)
assert struct.calcsize(self.format) == self.entrysize
self.zero = b'\0' * self.elementsize
self.store = bytearray(struct.pack(self.format, 0,
self.zero, self.zero)
) * self.size
def hash(self, k):
return hash(k) or 1
def stash(self, i, h, k, v):
entry = struct.pack(self.format, h, k, v)
self.store[i*self.entrysize:(i+1)*self.entrysize] = entry
def fetch(self, i):
entry = self.store[i*self.entrysize:(i+1)*self.entrysize]
return struct.unpack(self.format, entry)
def probe(self, keyhash):
i = keyhash % self.size
while True:
h, k, v = self.fetch(i)
yield i, h, k, v
i = (i + 1) % self.size
if i == keyhash % self.size:
break
__delitem__
エラー メッセージにあるように、抽象メソッド、__getitem__
、__iter__
、__len__
、およびの実装を提供する必要があります__setitem__
。ただし、より適切な場所は docsです。これらの 5 つのメソッド (および基本クラスに必要なその他のメソッド。ただし、表からわかるように、何もありません) を実装すると、次のようになります。他のすべての方法は無料です。それらすべてを可能な限り効率的に実装することはできないかもしれませんが、それについては後で説明します。
まず、対処しましょう__len__
。通常、人々はこれが O(1) であることを期待しています。つまり、必要に応じて更新し、独立して追跡する必要があります。そう:
class FixedDict(collections.abc.MutableMapping):
def __init__(self, elementsize, size):
self.hashtable = FixedHashTable(elementsize, size)
self.len = 0
次に、__getitem__
目的のキーが見つかるか最後に到達するまでプローブします。
def __getitem__(self, key):
keyhash = self.hashtable.hash(key)
for i, h, k, v in self.hashtable.probe(keyhash):
if h and k == key:
return v
And__delitem__
は同じことを行いますが、見つかった場合はスロットを空にし、 を更新しますlen
。
def __delitem__(self, key):
keyhash = self.hashtable.hash(key)
for i, h, k, v in self.hashtable.probe(keyhash):
if h and k == key:
self.hashtable.stash(i, 0, self.hashtable.zero, self.hashtable.zero)
self.len -= 1
return
raise KeyError(key)
__setitem__
少しトリッキーです。見つかった場合は、スロットの値を置き換える必要があります。そうでない場合は、空のスロットを埋める必要があります。ここで、ハッシュ テーブルがいっぱいになる可能性があるという事実に対処する必要があります。そしてもちろん、次のことを処理する必要がありlen
ます。
def __setitem__(self, key, value):
keyhash = self.hashtable.hash(key)
for i, h, k, v in self.hashtable.probe(keyhash):
if not h or k == key:
if not h:
self.len += 1
self.hashtable.stash(i, keyhash, key, value)
return
raise ValueError('hash table full')
そして、それは去り__iter__
ます。a と同じように、dict
特定の順序はありません。そのため、ハッシュ テーブル スロットを反復処理して、空でないものをすべて生成できます。
def __iter__(self):
return (k for (h, k, v) in self.hashtable.fetch(i)
for i in range(self.hashtable.size) if h)
私たちがそれをしている間、私たちは__repr__
. items
無料で入手できるという事実を使用できることに注意してください。
def __repr__(self):
return '{}({})'.format(type(self).__name__, dict(self.items()))
ただし、デフォルトitems
では単に が作成されることに注意してください。ソースを追跡すると、反復して各値を検索するItemsView(self)
ことがわかります。self
パフォーマンスが重要な場合は、明らかに改善できます。
def items(self):
pairs = ((k, v) for (h, k, v) in self.hashtable.fetch(i)
for i in range(self.hashtable.size) if h)
return collections.abc.ItemsView._from_iterable(pairs)
同様にvalues
、 、およびおそらく他の方法についても同様です。