-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhashSet.py
204 lines (170 loc) · 6.77 KB
/
hashSet.py
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
class HashSet:
"""A class to represent a set abstract data type."""
def __init__(self, contents=[]):
"""
Constructs all the necessary attributes for the person object.
Takes a list as a parameter & sets up items and numItems instance variables.
"""
self.items = [None] * 10
self.numItems = 0
# adds items form a given list to a items instance variable
for item in contents:
self.add(item)
class __Placeholder:
"""
A class to represent a Placeholder type.
Used for removing items that are not at the end of a chain.
"""
def __init__(self):
pass
def __eq__(self, other):
return False
def __add(item, items):
"""
Helper function responsible for:
- calculating an index (hashing and %);
- performing linear probing (collision resolution).
"""
# modulo (%) is required to keep hashes within list size range
idx = hash(item) % len(items)
loc = -1
# linear probing loop
while items[idx] != None:
if items[idx] == item:
return False
if (loc < 0) and (type(items[idx]) == HashSet.__Placeholder):
loc = idx
idx = (idx + 1) % len(items)
if loc < 0:
loc = idx
items[loc] = item
return True
def __rehash(oldList, newList):
"""
Helper function responsible for rehashing values.
Used when list size is changed due to a load factor reaching threshold.
"""
for x in oldList:
if (x != None) and (type(x) != HashSet.__Placeholder):
HashSet.__add(x, newList)
return newList
def __remove(item, items):
"""
Helper function responsible for removing items from a chain.
"""
idx = hash(item) % len(items)
# loop to go through the items in the chain starting at hashed index
while items[idx] != None:
if items[idx] == item:
nextIdx = (idx + 1) % len(items)
# substitute item with None if at the end of a chain
if items[nextIdx] == None:
items[idx] = None
# substitute item with Placeholder if not at the end of a chain
else:
items[idx] = HashSet.__Placeholder()
return True
idx = (idx + 1) % len(items)
return False
def __contains__(self, item):
"""
Magic function responsible for checking if an item belongs to a set
Invoked when "item in set" is executed.
Returns True if an item is in a set and False otherwise.
"""
idx = hash(item) % len(self.items)
# loop to go through the items in the chain starting at hashed index
while self.items[idx] != None:
if self.items[idx] == item:
return True
idx = (idx + 1) % len(self.items)
return False
def __iter__(self):
"""
Magic function responsible for iterating over items in set
Invoked when "for item in set" is executed.
"""
for i in range(len(self.items)):
# only yield items that are not None or Placeholders
if (self.items[i] != None) and (type(self.items[i]) != HashSet.__Placeholder):
yield self.items[i]
def __len__(self):
"""
Magic function responsible for returning the length of a set
Invoked when "len(set)" is executed
"""
return self.numItems
def add(self, item):
"""
Function responsible for adding items into a set.
Doubles items list size when load factor >= 75%.
"""
if HashSet.__add(item, self.items):
self.numItems += 1
load = self.numItems / len(self.items)
# double items list size of load factor >= 75%
if load >= 0.75:
self.items = HashSet.__rehash(self.items, [None]*2*len(self.items))
def remove(self, item):
"""
Function responsible for removing items from a set.
Halves items list size when load factor <= 25%.
In addition, raises an exception when item is not in a set.
"""
if HashSet.__remove(item, self.items):
self.numItems -= 1
load = max(self.numItems, 10) / len(self.items)
# halve items list size of load factor <= 25%
if load <= 0.25:
self.items = HashSet.__rehash(self.items, [None]*int(len(self.items)/2))
else:
raise KeyError("Item not in HashSet")
def discard(self, item):
"""
Function responsible for removing items from a set.
Halves items list size when load factor <= 25%.
Does not raise an exception when item is not in a set.
"""
if HashSet.__remove(item, self.items):
self.numItems -= 1
load = max(self.numItems, 10) / len(self.items)
if load <= 0.25:
self.items = HashSet.__rehash(self.items, [None]*int(len(self.items)/2))
def clear(self):
"""
Function responsible for removing all elements of a set.
Resets numItems instance variable to 0.
Resets items instance variable with an empty list.
"""
self.numItems = 0
self.items = [None] * 10
def update(self, other):
"""
Function responsible for adding the items from one set to another set.
"""
for item in other:
self.add(item)
def difference_update(self, other):
"""
Function responsible for subtracting from one set the elements of another set.
A = A - B
"""
for item in other:
self.discard(item)
def difference(self, other):
"""
Function responsible for subtracting from one set the elements of another set.
C = A - B
"""
result = HashSet(self)
result.difference_update(other)
return result
def issuperset(self, other):
"""
Function responsible for checking if one set is superset of another set
Returns True if a set is a superset of another set and False otherwise.
"""
for item in other:
if item not in self:
return False
return True