-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Expand file tree
/
Copy pathhashtable.py
More file actions
136 lines (109 loc) · 3.61 KB
/
hashtable.py
File metadata and controls
136 lines (109 loc) · 3.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# hashtable.py
from typing import Any, NamedTuple
DELETED = object()
class Pair(NamedTuple):
key: Any
value: Any
class HashTable:
@classmethod
def from_dict(cls, dictionary, capacity=None):
hash_table = cls(capacity or len(dictionary))
for key, value in dictionary.items():
hash_table[key] = value
return hash_table
def __init__(self, capacity=8, load_factor_threshold=0.6):
if capacity < 1:
raise ValueError("Capacity must be a positive number")
if not (0 < load_factor_threshold <= 1):
raise ValueError("Load factor must be a number between (0, 1]")
self._slots = capacity * [None]
self._load_factor_threshold = load_factor_threshold
def __len__(self):
return len(self.pairs)
def __iter__(self):
yield from self.keys
def __delitem__(self, key):
for index, pair in self._probe(key):
if pair is None:
raise KeyError(key)
if pair is DELETED:
continue
if pair.key == key:
self._slots[index] = DELETED
break
else:
raise KeyError(key)
def __setitem__(self, key, value):
if self.load_factor >= self._load_factor_threshold:
self._resize_and_rehash()
for index, pair in self._probe(key):
if pair is DELETED:
continue
if pair is None or pair.key == key:
self._slots[index] = Pair(key, value)
break
def __getitem__(self, key):
for _, pair in self._probe(key):
if pair is None:
raise KeyError(key)
if pair is DELETED:
continue
if pair.key == key:
return pair.value
raise KeyError(key)
def __contains__(self, key):
try:
self[key]
except KeyError:
return False
else:
return True
def __eq__(self, other):
if self is other:
return True
if type(self) is not type(other):
return False
return set(self.pairs) == set(other.pairs)
def __str__(self):
pairs = []
for key, value in self.pairs:
pairs.append(f"{key!r}: {value!r}")
return "{" + ", ".join(pairs) + "}"
def __repr__(self):
cls = self.__class__.__name__
return f"{cls}.from_dict({str(self)})"
def copy(self):
return HashTable.from_dict(dict(self.pairs), self.capacity)
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
@property
def pairs(self):
return {pair for pair in self._slots if pair not in (None, DELETED)}
@property
def values(self):
return [pair.value for pair in self.pairs]
@property
def keys(self):
return {pair.key for pair in self.pairs}
@property
def capacity(self):
return len(self._slots)
@property
def load_factor(self):
occupied_or_deleted = [slot for slot in self._slots if slot]
return len(occupied_or_deleted) / self.capacity
def _index(self, key):
return hash(key) % self.capacity
def _probe(self, key):
index = self._index(key)
for _ in range(self.capacity):
yield index, self._slots[index]
index = (index + 1) % self.capacity
def _resize_and_rehash(self):
copy = HashTable(capacity=self.capacity * 2)
for key, value in self.pairs:
copy[key] = value
self._slots = copy._slots