文章目錄
在文章【數據結構Python描述】使用列表手動實現一個字典類中,對UnsortedListMap
的5個主要方法進行效率分析後可知,使用列表這種數據結構來實現的映射效率較低,本文將使用哈希表這種數據結構實現的映射,經過這種方式實現的映射,其效率要遠高於前者。python
1、哈希表簡介
經過列表保存鍵值對實現的映射之因此效率較低,緣由在於最壞狀況下,__getitem__
、__setitem__
、__delitem__
、__iter__
方法均須要遍歷整個列表。算法
然而,因爲列表自然具備經過索引以 O ( 1 ) O(1) O(1)時間複雜度訪問其中元素的特色,所以能夠考慮是否能夠將鍵值對中的鍵經過某種轉換關係轉換成列表的索引值,而後將鍵值對保存在使用索引標識的列表單元處。數據結構
實際上,上面描述的這種特殊列表就是哈希表,而轉換關係就能夠認爲是哈希函數。app
1. 哈希函數簡介
所以,哈希函數 h h h的目標是爲了將每個鍵值對中的鍵 k k k轉換爲 [ 0 , N − 1 ] [0, N-1] [0,N−1]範圍內的一個整數 h ( k ) h(k) h(k)即哈希值,其中 N N N是哈希表 A A A這種特殊列表的容量,而鍵值對(k, v)
就保存在 A [ h ( k ) ] A[h(k)] A[h(k)]單元處。dom
在但願獲取某一個鍵值對時,只需先對 k k k執行相同的哈希函數得到哈希值,而後經過哈希值獲取哈希表對應單元處的鍵值對便可。ide
2. 哈希碰撞簡介
須要注意的是,針對不一樣的鍵,若是經過哈希函數獲得了相同的哈希值,這時就發生了所謂的哈希碰撞,此時須要對哈希表作額外的處理,這類額外處理通常都會下降哈希表的效率,具體的處理方案包括分離連接法、開放尋址法以及線性查找法等,後面將對其作詳細介紹。函數
3. 哈希函數深究
實際上,一般將哈希函數分爲兩個組成部分,即哈希碼和壓縮函數:測試
- 哈希碼負責將一個鍵 k k k轉換爲一個整數,該整數甚至能夠爲負;
- 壓縮函數負責將上述整數進一步轉換爲 [ 0 , N − 1 ] [0, N-1] [0,N−1]範圍內的一個整數。
將哈希函數分爲哈希碼和壓縮函數兩個部分的好處在於哈希碼的計算能夠獨立於哈希表的容量(和普通列表同樣,哈希表的容量須要根據其中元素的數量動態調整)。spa
這樣一來,咱們能夠對做爲鍵的任意不可變對象先經過一種統一的方式計算出其哈希碼,而後由壓縮函數使用哈希碼並根據哈希表的容量來計算該鍵對應的哈希值。.net
哈希碼
哈希函數的第一部分會先使用做爲鍵的任意不可變對象 k k k計算出一個整數,該整數被稱爲哈希碼。
Python有一個專門用於計算哈希碼的內建函數hash(x)
,該函數對任意對象x
可返回一個用做哈希碼的整數,而如本文所一直強調的,在Python中只有不可變類型纔是可哈希的。
這一限制可確保一個對象的哈希碼在其生命週期內保持不變,則該對象的哈希值在其生命週期內保持不變。
上述重要性質可避免這樣的可能異常狀況:將鍵值對插入哈希表時,鍵的哈希值對應表中某一個單元;在獲取一個鍵值對時,鍵的哈希值可能對應表中另一個單元。
在Python全部的內建數據類型中,因int
、float
、str
、tuple
、以及frozenset
的對象均爲不可變的,所以這些類型的對象都是可哈希的;而list
的對象可變,對於list
的實例對象x
,執行hash(x)
將拋出TypeError
異常。
壓縮函數
通常而言,鍵 k k k的哈希碼通常不能當即做爲哈希表的索引用,由於哈希碼可能爲負,甚至可能超過哈希表的容量。
所以,咱們須要一種方式可以將哈希碼映射成 [ 0 , N − 1 ] [0, N-1] [0,N−1]範圍內的一個證書,該方式就是壓縮函數。評價一個壓縮函數好壞的標準是:對於給定的一組各不相同的哈希碼,壓縮函數能夠將哈希碰撞的可能性降到最低。
下面是一些典型的壓縮函數:
取模運算法
一種簡單的壓縮函數爲取模運算法,即取:
i m o d N i \ mod \ N i mod N
其中 i i i爲哈希碼, N N N爲哈希表的容量,並且在 N N N爲質數時發生哈希碰撞的可能性將比較小。
當 N N N不是質數時,發生哈希碰撞的可能性將大大增長,例如:當一組鍵的哈希碼爲 { 200 , 205 , 210 , 215 , 220 , ⋅ ⋅ ⋅ , 600 } \{200, 205, 210, 215, 220, \cdot\cdot\cdot, 600\} { 200,205,210,215,220,⋅⋅⋅,600},且哈希表的容量爲100時,則每個哈希碼都將和另外3個哈希碼發生碰撞。
若是壓縮函數選擇得當,那麼兩個不一樣的哈希碼發生碰撞的機率爲 1 / N {\left. 1\middle/ N\right.} 1/N。
MAD方法
上述取模算法有一個缺點是,當哈希碼爲 p N + q pN+q pN+q的形式,則發生哈希碰撞的機率依然很大,而所謂的MAD(Multiply-Add-and-Divide)方法能夠很好的改善這個問題,即:
[ ( a i + b ) m o d p ] m o d N [(ai+b) \ mod \ p] \ mod \ N [(ai+b) mod p] mod N
其中 N N N依然爲哈希表的容量, p p p是比 N N N更大的質數,而 a a a和 b b b都是 [ 0 , p − 1 ] [0, p-1] [0,p−1]範圍內的整數且 a > 0 a\gt0 a>0。
4. 處理哈希碰撞
由上述討論可知,使用哈希表實現用於保存鍵值對的映射的主要思想在於:給定一個哈希表表 A A A,以及一個哈希函數 h h h,實現將鍵值對(k, v)
保存在哈希表的 A [ h ( k ) ] A[h(k)] A[h(k)]單元。
然而,問題在於:當對於兩個不一樣的鍵 k 1 k_1 k1和 k 2 k_2 k2有 h ( k 1 ) = h ( k 2 ) h(k_1)=h(k_2) h(k1)=h(k2),即發生了哈希碰撞,此時不能簡單的將鍵值對(k, v)
插入哈希表。一樣,在進行查找、刪除等操做時也須要對這種狀況進行考慮。
所以,下面就將介紹幾種處理哈希碰撞的方案:
分離連接法
處理哈希碰撞的一種簡單而高效的方式是分離連接法(Separate Chaining),即將全部知足 h ( k ) = j h(k)=j h(k)=j的鍵值對(k, v)
保存一個二級容器(如:使用列表保存鍵值對實現的映射對象)中,而哈希表的單元 A [ j ] A[j] A[j]引用該二級容器。
以下圖所示,一個容量爲13的哈希表保存了10個鍵值對(圖中省略了全部鍵值對中的值),且哈希函數爲 h ( k ) = k m o d 13 h(k)=k \ mod \ 13 h(k)=k mod 13:
對於上述處理哈希碰撞的方案,對某一個單元 A [ j ] A[j] A[j]所引用的二級容器進行查找時,在最壞的狀況下,時間複雜度和二級容器的大小呈正比。
假設如今有一個能儘可能下降哈希碰撞發生機率的哈希函數,待存儲的鍵值對數目爲 n n n,哈希表容量爲 N N N,則指望每個哈希表單元處引用的二級容器的容量爲 n / N {\left. n\middle/ N\right.} n/N。
基於以上設定,則映射__getitem__
、__setitem__
以及__delitem__
這三個核心方法的最壞時間複雜度爲 O ( ⌈ n / N ⌉ ) O(\lceil {\left. n\middle/ N\right.} \rceil) O(⌈n/N⌉)。通常將 λ = n / N \lambda={\left. n\middle/ N\right.} λ=n/N記爲哈希表的負載係數。顯然 λ \lambda λ最好應當小於1,不然必然會發生哈希碰撞,此時映射幾個核心方法的指望時間複雜度爲 O ( 1 ) O(1) O(1)。
線性查找法
上述解決哈希碰撞的分離連接法雖然實現起來較爲方便,可是其也有一個缺陷:須要一個輔助的二級容器來保存發生哈希碰撞的鍵值對。這對內存敏感的可穿戴設備不利。下面將要介紹的線性查找法就能夠確保不使用額外的二級容器來避免哈希衝突:
在使用線性查找法時,當嘗試將鍵值對(k, v)
向哈希表 A [ j ] A[j] A[j](其中 j = h ( k ) j=h(k) j=h(k))單元插入時發現後者已被佔用,則嘗試 A [ ( j + 1 ) m o d N ] A[(j+1) \ mod \ N] A[(j+1) mod N]單元。若是 A [ ( j + 1 ) m o d N ] A[(j+1) \ mod \ N] A[(j+1) mod N]依然已被佔用則嘗試 A [ ( j + 2 ) m o d N ] A[(j+2) \ mod \ N] A[(j+2) mod N],以此類推,直到在哈希表中找到空的單元。
爲解釋上述過程,假定哈希表容量爲11,鍵均爲整數,哈希函數爲 h ( k ) = k m o d 11 h(k) = k \ mod \ 11 h(k)=k mod 11,則下圖(省略鍵值對中的值)表示了在如圖狀態下插入鍵爲15的鍵值對所要嘗試的操做:
上述策略使得映射的三個核心方法__getitem__
、__setitem__
以及__delitem__
在實現的第一步就須要進行特別的考慮。
具體地,當實現__delitem__
時,咱們不能簡單地將找到的鍵值對進行刪除,例如:如插入鍵爲15的鍵值對後,若是刪除了鍵爲37的鍵值對,則後續對於鍵爲15的鍵值對的搜索就將失敗,由於線性查找策略將先從索引爲4的單元處開始,而後到索引爲5處,而後再發現索引爲6的單元爲空。所以:
- 對於
__delitem__
:在刪除某單元處的鍵值對後,將其引用一個用於標記的對象(通常爲object
的實例); - 對於
__getitem__
:在查找鍵值對時,當遇到object
的實例時就跳過,直到找到指望的鍵值對或空的單元,抑或回到查找開始的單元; - 對於
__setitem__
:
5. 負載係數和再哈希
負載係數
負載係數 λ = n / N \lambda={\left. n\middle/ N\right.} λ=n/N會極大地影響哈希表的操做效率,由於:
對於使用分離連接法處理哈希碰撞的哈希表,當 λ \lambda λ很是接近1時,發生哈希碰撞的概率就會大大增長,這會下降後續操做的效率,所以實驗代表這種哈希表的的負載係數應知足 λ < 0.9 \lambda\lt0.9 λ<0.9。
對於使用線性查找法處理哈希碰撞的哈希表,實驗證實當 λ \lambda λ從0.5開始接近1時,鍵值對將在哈希表一系列連續單元處發生簇集,此時也會下降後續操做的效率,所以實驗代表這種哈希表的負載係數應知足 λ < 0.5 \lambda\lt0.5 λ<0.5。
再哈希
爲了保持哈希表的負載係數在合理範圍內,當插入鍵值對後致使負載係數超過某範圍,則一般的作法都是先擴充哈希表的容量以下降負載係數,而後從新將全部鍵值對插入擴充後的哈希表中,這個過程就叫作再哈希。
實際上,因爲哈希函數被分爲兩個部分,而第一部分計算哈希碼的過程和哈希表的容量無關,所以再哈希時只須要經過壓縮函數計算處最終的哈希值(表明鍵值對應該保存的哈希表單元的索引)便可。
2、哈希表實現映射
根據使用的哈希函數在產生哈希碰撞時是採用分離連接法仍是線性查找法處理,下面給出兩種基於哈希表實現的兩個映射類。
1. 基於哈希表實現映射的基類HashMapBase
雖然處理哈希碰撞的兩種策略各異,可是經過兩者實現的映射依然具備不少共通之處,爲此這裏先將映射基類MapBase進行擴充獲得HashMapBase
,具體地:
- 映射中的鍵值對的哈希表用列表來表示,即映射中有
self._table
實例屬性,且全部單元初始化爲None
; - 映射中的鍵值對數量使用實例屬性
self._n
來表示; - 若是哈希表的負載係數超過0.5,則倍增哈希表容量且經過再哈希將全部鍵值對轉到新的哈希表中;
- 定義了一個新的實用方法
_hash_function()
,該方法基於Python內建的函數hash()
先計算出一個哈希碼,而後實用本文介紹的MAD方法做爲壓縮函數。
擴充後的基類HashMapBase
中留待具體子類實現的是如何表示哈希表的每個單元。對於分離連接法,一個單元是一個二級容器(如:列表、鏈表等);對於線性查找法,單元的含義不必定是哈希表一個索引表明的位置,有多是幾個索引表明的位置。
所以,這裏的HashMapBase
類假定下列方法爲抽象方法,留待具體子類實現:
_bucket_getitem(j, k)
:根據鍵 k k k的哈希值 j j j搜索第 j j j個單元處鍵爲 k k k的鍵值對,返回找到的鍵值對,不然拋出KeyError
異常;_bucket_setitem(j, k, v)
:根據鍵 k k k的哈希值 j j j修改第 j j j個單元處鍵爲 k k k的鍵值對,若是鍵 k k k已存在則修改已有的值,不然插入新的鍵值對而後爲self._n
加一;_bucket_delitem(j, k)
:根據鍵 k k k的哈希值 j j j刪除第 j j j個單元處鍵爲 k k k的鍵值對並將self._n
減一,如不存在該鍵值對則拋出KeyError
異常。
__init__
def __init__(self, cap=11, p=109345121): """建立一個空的映射""" self._table = cap * [None] self._n = 0 self._prime = p # MAD壓縮函數中大於哈希表容量的大質數 self._scale = 1 + randrange(p-1) # MAD壓縮函數中的縮放係數a self._shift = randrange(p) # MAD壓縮函數中的負載係數b
_hash_function
根據下列公式計算哈希值:
[ ( a i + b ) m o d p ] m o d N [(ai+b) \ mod \ p] \ mod \ N [(ai+b) mod p] mod N
def _hash_function(self, k): """哈希函數""" return (self._scale * hash(k) + self._shift) % self._prime % len(self._table)
__len__
def __len__(self): return self._n
__getitem__
def __getitem__(self, k): j = self._hash_function(k) return self._bucket_getitem(j, k) # 可能拋出KeyError異常
__setitem__
def __setitem__(self, k, v): j = self._hash_function(k) self._bucket_setitem(j, k, v) if self._n > len(self._table) // 2: # 確保負載係數小於0.5 self._resize(2 * len(self._table) - 1) # 一般2 * n - 1爲質數 def _resize(self, cap): """將哈希表容量調整爲cap""" old = list(self.items()) # 經過迭代得到已有的全部鍵值對 self._table = cap * [None] self._n = 0 for k, v in old: self[k] = v # 將鍵值對從新插入新的哈希表中
須要注意的是,items()
方法來源於繼承鏈上的Mapping
類,在Mapping
類中該方法的返回值是ItemsView(self)
(self
是一個映射對象)。
而ItemsView
的初始化方法直接繼承自MappingView
,所以映射對象用以初始化ItemsView
的實例,由於其初始化方法具體爲:
def __init__(self, mapping): self._mapping = mapping
所以,ItemsView
有一個實例屬性_mapping
爲映射實例對象,而ItemsView
的實例又是一個迭代器對象,由於其中的__iter__
方法使用yield (key, self._mapping[key])
。
__delitem__
def __delitem__(self, k): j = self._hash_function(k) self._bucket_delitem(j, k) # 可能拋出KeyError異常 self._n -= 1
2. 基於分離連接法實現的映射ChainHashMap
下面先給出基於分離連接法處理哈希碰撞實現的映射類ChainHashMap
,在其下面實現的前三個方法均使用索引j
來訪問哈希表的某單元處,且檢查該單元處是否爲空(即引用None
),只有在調用_bucket_setitem
且當該單元處爲空時才須要使之引用一個二級容器(這裏使用的是基於普通列表實現的映射UnsortedListMap
的的實例)。
_bucket_getitem(j, k)
def _bucket_getitem(self, j, k): bucket = self._table[j] if bucket is None: raise KeyError('Key Error: ' + repr(k)) return bucket[k]
_bucket_setitem(j, k, v)
須要注意的是,當鍵k對應的鍵值對第一次插入映射中時,須要將映射中鍵值對的數量加一。
def _bucket_setitem(self, j, k, v): if self._table[j] is None: self._table[j] = UnsortedListMap() # 使得哈希表該單元處引用一個二級容器 oldsize = len(self._table[j]) self._table[j][k] = v if len(self._table[j]) > oldsize: # k爲新的鍵 self._n += 1
_bucket_delitem(j, k)
def _bucket_delitem(self, j, k): bucket = self._table[j] if bucket is None: raise KeyError('Key Error: ' + repr(k)) del bucket[k]
__iter__()
須要注意的是,映射對象的每個非空單元處都引用了一個二級容器,這裏使用的二級容器是UnsortedListMap
,其是一個迭代器。
def __iter__(self): for bucket in self._table: if bucket is not None: for key in bucket: yield key
3. 基於線性查找法實現的映射ProbeHashMap
_is_available(j)
def _is_available(self, j): """當哈希表索引爲j的單元處爲空或鍵值對被刪除,則返回True""" return self._table[j] is None or self._table[j] is ProbeHashMap._AVAIL
_find_slot(j, k)
def _find_slot(self, j, k): """查找索引爲j的哈希表單元處是否有鍵k 該方法的返回值爲一個元組,且返回的狀況以下: - 當在索引爲j的哈希表單元處找到鍵k,則返回(True, fisrt_avail); - 當未在哈希表任何單元處找到鍵k,則返回(False, j)。 """ first_avail = None while True: if self._is_available(j): if first_avail is None: first_avail = j if self._table[j] is None: return False, first_avail elif k == self._table[j].key: return True, j j = (j + 1) % len(self._table)
_bucket_getitem(j, k)
def _bucket_getitem(self, j, k): found, s = self._find_slot(j, k) if not found: raise KeyError('Key Error: ' + repr(k)) return self._table[s].value
_bucket_setitem(j, k, v)
def _bucket_setitem(self, j, k, v): found, s = self._find_slot(j, k) if not found: self._table[s] = self._Item(k, v) self._n += 1 else: self._table[s].value = v
_bucket_delitem(j, k)
def _bucket_delitem(self, j, k): found, s = self._find_slot(j, k) if not found: raise KeyError('Key Error: ' + repr(k)) self._table[s] = ProbeHashMap._AVAIL
__iter__()
def __iter__(self): for j in range(len(self._table)): if not self._is_available(j): yield self._table[j].key
3、哈希表實現映射的效率
操做 | 基於普通列表的效率 | 基於哈希表的指望效率 | 基於哈希表的最壞效率 |
---|---|---|---|
__getitem__ |
O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | O ( n ) O(n) O(n) |
__setitem__ |
O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | O ( n ) O(n) O(n) |
__delitem__ |
O ( n ) O(n) O(n) | O ( 1 ) O(1) O(1) | O ( n ) O(n) O(n) |
__len__ |
O ( 1 ) O(1) O(1) | O ( 1 ) O(1) O(1) | O ( 1 ) O(1) O(1) |
__iter__ |
O ( n ) O(n) O(n) | O ( n ) O(n) O(n) | O ( n ) O(n) O(n) |
4、哈希表實現映射的測試代碼
from abc import ABC, abstractmethod from collections.abc import MutableMapping from random import randrange class MapBase(MutableMapping, ABC): """提供用於保存鍵值對記錄類的自定義映射基類""" class _Item: __slots__ = 'key', 'value' def __init__(self, key, value): self.key = key self.value = value def __eq__(self, other): return self.key == other.key # 使用'=='語法基於鍵比較兩個鍵值對是否相等 def __ne__(self, other): return not (self == other) # 使用'!='語法基於鍵比較兩個鍵值對是否不等 def __lt__(self, other): return self.key < other.key # 使用'<'語法基於鍵比較兩個鍵值對 class UnsortedListMap(MapBase): def __init__(self): """建立一個空的映射對象""" self._table = [] # 映射中的鍵值對記錄保存在列表中 def __getitem__(self, key): """返回與鍵key關聯的值value,當鍵key不存在則拋出KeyError異常""" for item in self._table: if key == item.key: return item.value raise KeyError('key error: ', repr(key)) def __setitem__(self, key, value): """將key-value添加至映射對象中,當存在鍵值key時將其值替換爲value""" for item in self._table: # 遍歷查詢映射中是否存在鍵key if key == item.key: item.value = value return self._table.append(self._Item(key, value)) # 映射中不存在鍵key def __delitem__(self, key): """刪除鍵key表明的鍵值對,當鍵key不存在則拋出KeyError異常""" for j in range(len(self._table)): # 遍歷查詢映射中是否存在鍵key if key == self._table[j].key: self._table.pop(j) return raise KeyError('key error: ', repr(key)) # 映射中不存在鍵key def __len__(self): """返回映射中鍵值對數量""" return len(self._table) def __iter__(self): """生成一個映射中全部鍵的迭代""" for item in self._table: yield item.key def __str__(self): """返回映射對象的字符串表示形式""" return str(dict(self.items())) class HashMapBase(MapBase): """使用哈希表實現映射的基類""" def __init__(self, cap=11, p=109345121): """建立一個空的映射""" self._table = cap * [None] self._n = 0 self._prime = p # MAD壓縮函數中大於哈希表容量的大質數 self._scale = 1 + randrange(p-1) # MAD壓縮函數中的縮放係數a self._shift = randrange(p) # MAD壓縮函數中的便宜係數b def _hash_function(self, k): """哈希函數""" return (self._scale * hash(k) + self._shift) % self._prime % len(self._table) @abstractmethod def _bucket_getitem(self, j, k): pass @abstractmethod def _bucket_setitem(self, j, k, v): pass @abstractmethod def _bucket_delitem(self, j, k): pass def __len__(self): return self._n def __getitem__(self, k): j = self._hash_function(k) return self._bucket_getitem(j, k) # 可能拋出KeyError異常 def __setitem__(self, k, v): j = self._hash_function(k) self._bucket_setitem(j, k, v) if self._n > len(self._table) // 2: # 確保負載係數小於0.5 self._resize(2 * len(self._table) - 1) # 一般2 * n - 1爲質數 def __delitem__(self, k): j = self._hash_function(k) self._bucket_delitem(j, k) # 可能拋出KeyError異常 self._n -= 1 def _resize(self, cap): """將哈希表容量調整爲cap""" old = list(self.items()) # 經過迭代得到已有的全部鍵值對 self._table = cap * [None] self._n = 0 for k, v in old: self[k] = v # 將鍵值對從新插入新的哈希表中 class ChainHashMap(HashMapBase): """使用分離連接法處理哈希碰撞實現的哈希映射""" def _bucket_getitem(self, j, k): bucket = self._table[j] if bucket is None: raise KeyError('Key Error: ' + repr(k)) return bucket[k] def _bucket_setitem(self, j, k, v): if self._table[j] is None: self._table[j] = UnsortedListMap() # 使得哈希表該單元處引用一個二級容器 oldsize = len(self._table[j]) self._table[j][k] = v if len(self._table[j]) > oldsize: # k爲新的鍵 self._n += 1 def _bucket_delitem(self, j, k): bucket = self._table[j] if bucket is None: raise KeyError('Key Error: ' + repr(k)) del bucket[k] def __iter__(self): for bucket in self._table: if bucket is not None: for key in bucket: yield key class ProbeHashMap(HashMapBase): """使用線性查找法處理哈希碰撞實現的哈希映射""" _AVAIL = object() # 哨兵標識,用於標識被鍵值對被刪除的哈希表單元 def _is_available(self, j): """當哈希表索引爲j的單元處爲空或鍵值對被刪除,則返回True""" return self._table[j] is None or self._table[j] is ProbeHashMap._AVAIL def _find_slot(self, j, k): """查找索引爲j的哈希表單元處是否有鍵k 該方法的返回值爲一個元組,且返回的狀況以下: - 當在索引爲j的哈希表單元處找到鍵k,則返回(True, fisrt_avail); - 當未在哈希表任何單元處找到鍵k,則返回(False, j)。 """ first_avail = None while True: if self._is_available(j): if first_avail is None: first_avail = j if self._table[j] is None: return False, first_avail elif k == self._table[j].key: return True, j j = (j + 1) % len(self._table) def _bucket_getitem(self, j, k): found, s = self._find_slot(j, k) if not found: raise KeyError('Key Error: ' + repr(k)) return self._table[s].value def _bucket_setitem(self, j, k, v): found, s = self._find_slot(j, k) if not found: self._table[s] = self._Item(k, v) self._n += 1 else: self._table[s].value = v def _bucket_delitem(self, j, k): found, s = self._find_slot(j, k) if not found: raise KeyError('Key Error: ' + repr(k)) self._table[s] = ProbeHashMap._AVAIL def __iter__(self): for j in range(len(self._table)): if not self._is_available(j): yield self._table[j].key