Skip to content

Commit 49f326d

Browse files
committed
Hashtables and Hashmaps
1 parent c89ef40 commit 49f326d

File tree

4 files changed

+158
-0
lines changed

4 files changed

+158
-0
lines changed

LeetCode/ArraysAndHashing/ValidSudoku.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def isValidSudoku(self, board: List[List[str]]) -> bool:
3737
- After processing all the cells, the method checks if the length of res is equal to the
3838
length of the set of res
3939
40+
!Note: SETS CANNOT HAVE DUPLICATE ELEMENTS HENCE WHY WE CHECK LEN(RES) AND LEN(SET(RES))
4041
Because different length on res and set(res) means that there are the same tuples in res.
4142
Moreover, we need to notice that tuples representing different groups are never equal
4243
(since tuple for row is Tuple[int, str] type, tuple for column is Tuple[str, int]
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import HashMapBase
2+
import UnsortedTableMap
3+
class ChainHashMap(HashMapBase):
4+
"""Hash map implementation using Separate Chaining Collision handling"""
5+
6+
def _bucket_getitem(self, j, k):
7+
bucket= self._table[j] #bucket is a list at position j
8+
if bucket is None: #if bucket does not exist
9+
raise KeyError("Key Error" + repr(k)) #no match found because bucket does not exist
10+
11+
def _bucket_setitem(self, j, k, v):
12+
if self._table[j] is None:
13+
self._table[j] = UnsortedTableMap() # creates a new bucket (unsorted table) to the table
14+
15+
oldsize = len(self._table[j])
16+
17+
self._table[j][k] = v #assign value to the key k on bucket j
18+
19+
if len(self._table[j]) > oldsize: # if bucket at j is > then oldsize new element was inserted so increase n
20+
self._n +=1
21+
22+
def _bucket_delitem(self, j, k):
23+
bucket = self._table[j]
24+
25+
if bucket is None:
26+
"""if bucket does not exist raise error"""
27+
raise KeyError("Key error" + repr(k))
28+
29+
del bucket[k] # delete item with key k if key k exists in bucket j
30+
31+
def __iter__(self):
32+
"""Iterator to retrieve keys in hashmap without deleting them"""
33+
for bucket in self._table:
34+
if bucket is not None:
35+
for key in bucket:
36+
yield key
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from random import randrange
2+
import MapBase
3+
4+
class HashMapBase(MapBase):
5+
"""ADT class for map using Hash-Tables with MAD compression"""
6+
7+
def __init__(self, cap=11, p=109345121):
8+
"""Create an empty hashtable map
9+
cap - capacity
10+
p - positive prime numbver used for MAD by default """
11+
12+
self._table=cap * [None] # creates an empty list containing 11 entries of value None
13+
self._n=0 # no items present in the list; number of entries is 0 by default
14+
self._prime= p
15+
self._scale = 1 + randrange(p-1) #scale from 1 to p-1 for MAD picks a random number
16+
self._shift = randrange(p) #shift from 0 to p-1 for MAD
17+
18+
def _hash_function(self, k):
19+
"""Performs Python's built in calaulation for creating the hashcode of a key
20+
hash_function(k) """
21+
22+
return (hash(k) * self._scale + self._shift) % self._prime % len(self._table)
23+
24+
def __length__(self):
25+
return self._n #returns the number of distinct items present in the table at the time of method call.
26+
27+
def __getitem__(self, k):
28+
j = self._hash_function(k) # j holds hash code of the key k
29+
30+
return self,self._bucket_getitem(j, k) # may raise key error if hashcode does not exist for a key k
31+
32+
def __Setitem__(self, k,v):
33+
j = self._hash_function(k)
34+
self._bucket_setitem(j,k,v) #sub routine maintains self._n
35+
# increase n if item is added to hashtable
36+
if len(self._table) // 2 < self._n: # keep load factor of hash table under 0.5, if surpasses
37+
self._resize(2 * len(self._table) -1 ) #resise table by creating a new table double the size
38+
# 2 * x - 1 is often a prime number
39+
def __delitem__(self, k):
40+
j= self._hash_function(k)
41+
self._bucket_delitem(j, k) #may raise key error if hashcode does not match to the key k
42+
self._n -=1 # decrease number of items
43+
44+
def _resise(self, c):
45+
"""if load factor passes 0.5 create a new table with capacity c size as long as that capacity c
46+
will have the load factor under 0.5 and copy all items to the new table
47+
This is done to keep the load factor under or equal to 0.5 for better collision handling."""
48+
49+
old=list(self.items()) # use iteration to record existing items
50+
self._table = c * [None] # reset table to desirable capacity
51+
self._n = 0 # n recomputed during subsequent adds
52+
for (k,v) in old:
53+
self[k] = v #reinsert old key-value pair into resized table
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""
2+
Open addressing with linear probing.
3+
In order to support deletions, we place a special marker in a table location at
4+
which an item has been deleted, so that we can distinguish between it and a location
5+
that has always been empty
6+
7+
OPPEN ADDRESSING CHALLENGE:
8+
- properly trace the series of probes when collisions occur during an insertion or
9+
search for an item."""
10+
import HashMapBase
11+
class ProbeHashMap(HashMapBase):
12+
"""HashMap implemented with linear probing for collision resolutions"""
13+
14+
_AVAIL = object() #sentinel marks location of previous deletion
15+
16+
def _is_available(self, j):
17+
"""Return true if index j is available in the table"""
18+
return self._table[j] is None or self._table[j] is ProbeHashMap._AVAIL
19+
20+
def _find_slot(self, j ,k):
21+
"""search for key k in bucket at index j
22+
23+
Return (success, index) tuple, described as follow:
24+
If match was found, success is True and index denotes its location
25+
If no match found, success is False and index denotes first available slots """
26+
27+
firstAvail= None
28+
29+
while True:
30+
if firstAvail is None:
31+
firstAvail = j #mark this as first available
32+
33+
if self._table[j] is None:
34+
return (False, firstAvail) #search has failed
35+
elif k == self._table[j]._key:
36+
return (True , j) #found a match
37+
38+
j = (j+1) % len(self._table) #keep looking cyclically
39+
40+
def _bucket_getitem(self, j ,k):
41+
found, s = self._find_slot(j, k) #find slot at bucket j the key k to retrieve the Item
42+
43+
if not found:
44+
raise KeyError("Key Error" + repr(k)) #no match found
45+
46+
return self._table[s]._value #return the element at position s of the hashtable
47+
48+
def _bucket_setitem(self, j, k, v):
49+
found, s = self._find_slot(j,k) #find at bucket j the key k
50+
51+
if not found:
52+
self._table[s] = self._Item(k,v) #insert new item
53+
self._n +=1 # increase size
54+
else:
55+
self._table[s]._value= v #overwrite existing
56+
57+
def _bucket_delitem(self, j , k):
58+
found, s = self._find_slot(j,k) # find slot in j with key k to delete
59+
60+
if not found:
61+
raise KeyError("Key error" + repr(k))
62+
63+
self._table[s] = ProbeHashMap._AVAIL #mark slot as vacated after deletion
64+
65+
def __iter__(self):
66+
for j in range(len(self._table)): # Scan the entire table
67+
if not self._is_available(j):
68+
yield self._table[j]._key # yields every key in hashtable when iterating

0 commit comments

Comments
 (0)