Skip to content

Commit 58e7f79

Browse files
committed
Merge branch 'master' of github.com:CJuanvip/fluent_python_implementation
2 parents 35bccd1 + 74867d3 commit 58e7f79

File tree

1 file changed

+223
-0
lines changed

1 file changed

+223
-0
lines changed

Python-Features/HashImplementation.py

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
def custom_hash(key):
2+
"""
3+
Return the hash value of the given key. Uses dbj2
4+
@param key: String or unicode
5+
"""
6+
result = 5381
7+
multiplier = 33
8+
9+
if isinstance(key, int):
10+
return key
11+
12+
for char in key:
13+
result = 33 * result + ord(char)
14+
return result
15+
16+
class Hash(object):
17+
def __init__(self, size=8, hashfunction=custom_hash):
18+
# Total block size which can be array or list
19+
self._size = 8
20+
# Initial hashtable size
21+
self.__initial_size = self._size
22+
# Counter for holding total used slots
23+
self._used_slots = 0
24+
# Counter for holding deleted keys
25+
self._dummy_slots = 0
26+
# Holds all the keys
27+
self._keys = [None] * self._size
28+
# Holds all the values
29+
self._values = [None] * self._size
30+
# Alias for custom_hash function
31+
self.hash = custom_hash
32+
# threshold is used for increasing hash table
33+
self._max_threshold = 0.70
34+
35+
def should_expand(self):
36+
"""Returns True or False
37+
38+
If used slots and dummy slots are more than 70% resize the hash table.
39+
"""
40+
return (float(self._used_slots + self._dummy_slots) / self._size) >= self._max_threshold
41+
42+
def _probing(self, current_position):
43+
"""Quadratic probing to get new position when collision occurs.
44+
@param current_position: position at already element is present.
45+
"""
46+
# Algorithm is copied from CPython http://hg.python.org/cpython/file/52f68c95e025/Objects/dictobject.c#l69
47+
return ((5 + current_position) + 1) % self._size
48+
49+
def _set_item_at_pos(self, position, key, value):
50+
self._keys[position] = key
51+
self._values[position] = value
52+
53+
self._used_slots += 1
54+
55+
def _set_item(self, position, key, value):
56+
"""sets key and value in the given position.
57+
If position has already value in it, calls _probing to get next position
58+
@param position: index
59+
@param key: key
60+
@param value: value
61+
"""
62+
existing_key = self._keys[position]
63+
64+
if existing_key is None or existing_key == key:
65+
# Empty or update
66+
self._set_item_at_pos(position, key, value)
67+
else:
68+
# Collision needs a probing. This needs to be recursive.
69+
new_position = self._probing(position)
70+
self._set_item(new_position, key, value)
71+
72+
def _reposition(self, keys, values):
73+
"""Reposition all the keys and values.
74+
This is called whenever load factor or threshold has crossed the limit.
75+
"""
76+
for (key, value) in zip(keys, values):
77+
if key is not None:
78+
hashvalue = self.hash(key)
79+
position = self._calculate_position(hashvalue)
80+
81+
self._set_item(position, key, value)
82+
83+
def _resize(self):
84+
old_keys = self._keys
85+
old_values = self._values
86+
87+
# New size
88+
self._size = self._size * 4
89+
90+
# create new block of memory and clean up old keys positions
91+
self._keys = [None] * self._size
92+
self._values = [None] * self._size
93+
self._used_slots = 0
94+
self._dummy_slots = 0
95+
96+
# Now reposition the keys and values
97+
98+
self._reposition(old_keys, old_values)
99+
100+
def _calculate_position(self, hashvalue):
101+
return hashvalue % self._size
102+
103+
def raise_if_not_acceptable_key(self, key):
104+
if not isinstance(key, (basestring, int)):
105+
raise TypeError("Key should be int or string or unicode")
106+
107+
def put(self, key, value):
108+
"""Given a key and value add to the hashtable.
109+
Key should be int or string or unicode.
110+
"""
111+
self.raise_if_not_acceptable_key(key)
112+
113+
if self.should_expand():
114+
self._resize()
115+
116+
position = self._calculate_position(self.hash(key))
117+
self._set_item(position, key, value)
118+
119+
def _get_pos_recursively(self, position, key):
120+
new_position = self._probing(position)
121+
tmp_key = self._keys[new_position]
122+
123+
if tmp_key == None:
124+
# At new position the key is empty raise ane exception
125+
raise KeyError(u"{} key not found".format(key))
126+
elif tmp_key != key:
127+
# Again check for next position
128+
return self._get_pos_recursively(new_position, key)
129+
else:
130+
return new_position
131+
132+
def _get_pos(self, key):
133+
"""
134+
Returns position of the key
135+
"""
136+
self.raise_if_not_acceptable_key(key)
137+
position = self._calculate_position(self.hash(key))
138+
139+
tmp_key = self._keys[position]
140+
141+
if tmp_key == None:
142+
raise KeyError("{} doesn't exist".format(key))
143+
144+
elif tmp_key != key:
145+
# Probably collision and get next position using probing
146+
return self._get_pos_recursively(position, key)
147+
else:
148+
return position
149+
150+
def get(self, key):
151+
position = self._get_pos(key)
152+
153+
if position is None:
154+
return None
155+
156+
return self._values[position]
157+
158+
def _delete_item(self, position, key):
159+
self._keys[position] = None
160+
self._values[position] = None
161+
162+
self._dummy_slots += 1
163+
164+
def delete(self, key):
165+
"""Deletes the key if present. KeyError is raised if Key is missing.
166+
"""
167+
position = self._get_pos(key)
168+
169+
if position is None:
170+
raise KeyError(key)
171+
172+
self._delete_item(position, key)
173+
174+
# Downsizing of hash map is not yet implemented.
175+
176+
if __name__ == "__main__":
177+
h = Hash()
178+
h.put("a", 1)
179+
assert h.get("a") == 1
180+
h.put("a", 23)
181+
assert h.get("a") == 23
182+
h.put("b", 2)
183+
assert h.get("b") == 2
184+
h.put("z", 26)
185+
#print h._values
186+
#print h._keys
187+
assert h.get("z") == 26
188+
h.put("c", 3)
189+
assert h.get("c") == 3
190+
h.put("d", 4)
191+
assert h.get("d") == 4
192+
h.put("e", 5)
193+
assert h.get("e") == 5
194+
h.put("f", 6)
195+
assert h.get("f") == 6
196+
h.put("g", 7)
197+
assert h.get("g") == 7
198+
h.put("h", 8)
199+
assert h.get("h") == 8
200+
h.put("i", 9)
201+
assert h.get("i") == 9
202+
h.put("j", 10)
203+
assert h.get("j") == 10
204+
h.put(1, 1)
205+
assert h.get(1) == 1
206+
h.put(6, 6)
207+
#print h.get(6)
208+
assert h.get(6) == 6
209+
h.put("krace", [24, 12])
210+
assert h.get("krace") == [24, 12]
211+
h.delete("krace")
212+
try:
213+
h.get("krace")
214+
except KeyError:
215+
pass
216+
h.delete("a")
217+
h.delete("b")
218+
h.delete("c")
219+
h.delete("d")
220+
h.delete("e")
221+
h.delete("f")
222+
h.delete("g")
223+
print "All tests passed"

0 commit comments

Comments
 (0)