-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhash_map_sc.py
477 lines (405 loc) · 15.4 KB
/
hash_map_sc.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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
# Name: Kristin Eberman
# OSU Email: [email protected]
# Course: CS261 - Data Structures (section 404)
# Assignment: 6 - HashMap Implementation - Chaining
# Due Date: December 2, 2022
# Description: This assignment implements 10 methods within the HashMap class
# using a DynamicArray to store the hash table and chaining of singly
# linked lists for collision resolution
from a6_include import (DynamicArray, LinkedList,
hash_function_1, hash_function_2)
class HashMap:
def __init__(self,
capacity: int = 11,
function: callable = hash_function_1) -> None:
"""
Initialize new HashMap that uses
separate chaining for collision resolution
DO NOT CHANGE THIS METHOD IN ANY WAY
"""
self._buckets = DynamicArray()
# capacity must be a prime number
self._capacity = self._next_prime(capacity)
for _ in range(self._capacity):
self._buckets.append(LinkedList())
self._hash_function = function
self._size = 0
def __str__(self) -> str:
"""
Override string method to provide more readable output
DO NOT CHANGE THIS METHOD IN ANY WAY
"""
out = ''
for i in range(self._buckets.length()):
out += str(i) + ': ' + str(self._buckets[i]) + '\n'
return out
def _next_prime(self, capacity: int) -> int:
"""
Increment from given number and the find the closest prime number
DO NOT CHANGE THIS METHOD IN ANY WAY
"""
if capacity % 2 == 0:
capacity += 1
while not self._is_prime(capacity):
capacity += 2
return capacity
@staticmethod
def _is_prime(capacity: int) -> bool:
"""
Determine if given integer is a prime number and return boolean
DO NOT CHANGE THIS METHOD IN ANY WAY
"""
if capacity == 2 or capacity == 3:
return True
if capacity == 1 or capacity % 2 == 0:
return False
factor = 3
while factor ** 2 <= capacity:
if capacity % factor == 0:
return False
factor += 2
return True
def get_size(self) -> int:
"""
Return size of map
DO NOT CHANGE THIS METHOD IN ANY WAY
"""
return self._size
def get_capacity(self) -> int:
"""
Return capacity of map
DO NOT CHANGE THIS METHOD IN ANY WAY
"""
return self._capacity
# ------------------------------------------------------------------ #
def put(self, key: str, value: object) -> None:
"""
Adds or updates a key/value pair in the hash map
:param key: Unique key associated with value
:param value: Value associated with key
:return: None
"""
# Resize the array if load factor >= 1
if self.table_load() >= 1:
self.resize_table(self._capacity * 2)
# Use hash function to generate hash value and associated index
hash = self._hash_function(key)
index = hash % self._capacity
# Iterate through the Linked List stored at relevant index
for _ in self._buckets[index]:
# If the given key already exists, replace its value
if _.key == key:
_.value = value
return
# If key does not yet exist, insert new key/value pair & update size
self._buckets[index].insert(key, value)
self._size += 1
def empty_buckets(self) -> int:
"""
Returns the number of empty buckets in the hash table
:return: Integer value that represents number of empty buckets
"""
# Initialize empty bucket value to 0
empty_buckets = 0
# Iterate over the dynamic array
for i in range(self._capacity):
# If bucket's length is 0, it is empty -> increment empty_buckets
if self._buckets[i].length() == 0:
empty_buckets += 1
return empty_buckets
def table_load(self) -> float:
"""
Returns the current hash table load factor
:return: Number of elements (size) / number of buckets (capacity)
"""
return self._size / self._capacity
def clear(self) -> None:
"""
Clears the contents of the HashMap without changing its capacity
:return: None
"""
# Override _buckets with empty DynamicArray of LinkedLists
self._buckets = DynamicArray()
for _ in range(self._capacity):
self._buckets.append(LinkedList())
# Reset size to 0
self._size = 0
def resize_table(self, new_capacity: int) -> None:
"""
Changes capacity of the hash table; rehashes all key/value pairs
:param new_capacity: New value for capacity of underlying array
:return: None
"""
# If the new_capacity < 1, don't resize
if new_capacity < 1:
return
# Initialize HashMap with new capacity
resized_hash = HashMap(new_capacity, self._hash_function)
# Re-set capacity to 2 if it's skipped when building resized_hash
if new_capacity == 2:
resized_hash._buckets.pop()
resized_hash._capacity -= 1
# Iterate all elements in the original array and put in resized_hash
for i in range(self._capacity):
for _ in self._buckets[i]:
resized_hash.put(_.key, _.value)
# Update/re-point the original buckets and capacity variables
self._capacity = resized_hash._capacity
self._buckets = resized_hash._buckets
def get(self, key: str) -> object:
"""
Returns the value associated with a given key
:param key: Key to search for in the HashMap
:return: Value of given key if it exists, otherwise None
"""
# Use hash function to find index of key
hash = self._hash_function(key)
index = hash % self._capacity
# Iterate over the LinkedList stored at relevant index
for _ in self._buckets[index]:
# Return value if the key is found
if _.key == key:
return _.value
return None
def contains_key(self, key: str) -> bool:
"""
Searches for a given key within the HashMap
:param key: Key to search for
:return: True if the key is found, otherwise False
"""
if self.get(key) is None:
return False
return True
def remove(self, key: str) -> None:
"""
Removes key/value pair if key exists in the HashMap
:param key: Key to search for and remove
:return: None
"""
# Use hash function to find index of key
hash = self._hash_function(key)
index = hash % self._capacity
# Iterate over the LinkedList stored at relevant index
for _ in self._buckets[index]:
# If the key exists, remove the key/value pair and decrement size
if _.key == key:
self._buckets[index].remove(key)
self._size -= 1
return
def get_keys_and_values(self) -> DynamicArray:
"""
Creates a dynamic array that contains tuples of all key/value pairs
stored in the HashMap
:return: Dynamic array of key/value tuples
"""
# Initialize an empty dynamic array to fill with tuples
key_value_arr = DynamicArray()
# Iterate over the HashMap to append each key/value tuple to the array
for i in range(self._capacity):
for _ in self._buckets[i]:
key_value_arr.append((_.key, _.value))
return key_value_arr
def find_mode(da: DynamicArray) -> (DynamicArray, int):
"""
Finds the most occurring value(s) (mode) in a given dynamic array and
how many times the mode value(s) appear
:param da: Dynamic array to find the mode value(s)
:return: Tuple containing a dynamic array with the mode value/s of the
array and an integer that represents the highest frequency
"""
# Initialize variables to return
mode_arr = DynamicArray()
highest_frequency = 0
# Set up HashMap functionality
map = HashMap()
# Iterate over the dynamic array
for i in range(da.length()):
# Set DA element (key)'s value to how many times it's in the HashMap
value = map.get(da[i])
value = 1 if not value else value + 1
# Insert the key/value pair into the HashMap
map.put(da[i], value)
# Update highest_frequency and reset mode_arr if necessary
if value > highest_frequency:
highest_frequency = value
mode_arr = DynamicArray()
# If key is one of the mode values, append it to the mode_arr
if value == highest_frequency:
mode_arr.append(da[i])
return mode_arr, highest_frequency
# ------------------- BASIC TESTING ---------------------------------------- #
if __name__ == "__main__":
print("\nPDF - put example 1")
print("-------------------")
m = HashMap(53, hash_function_1)
for i in range(150):
m.put('str' + str(i), i * 100)
if i % 25 == 24:
print(m.empty_buckets(), round(m.table_load(), 2), m.get_size(), m.get_capacity())
print("\nPDF - put example 2")
print("-------------------")
m = HashMap(41, hash_function_2)
for i in range(50):
m.put('str' + str(i // 3), i * 100)
if i % 10 == 9:
print(m.empty_buckets(), round(m.table_load(), 2), m.get_size(), m.get_capacity())
print("\nPDF - empty_buckets example 1")
print("-----------------------------")
m = HashMap(101, hash_function_1)
print(m.empty_buckets(), m.get_size(), m.get_capacity())
m.put('key1', 10)
print(m.empty_buckets(), m.get_size(), m.get_capacity())
m.put('key2', 20)
print(m.empty_buckets(), m.get_size(), m.get_capacity())
m.put('key1', 30)
print(m.empty_buckets(), m.get_size(), m.get_capacity())
m.put('key4', 40)
print(m.empty_buckets(), m.get_size(), m.get_capacity())
print("\nPDF - empty_buckets example 2")
print("-----------------------------")
m = HashMap(53, hash_function_1)
for i in range(150):
m.put('key' + str(i), i * 100)
if i % 30 == 0:
print(m.empty_buckets(), m.get_size(), m.get_capacity())
print("\nPDF - table_load example 1")
print("--------------------------")
m = HashMap(101, hash_function_1)
print(round(m.table_load(), 2))
m.put('key1', 10)
print(round(m.table_load(), 2))
m.put('key2', 20)
print(round(m.table_load(), 2))
m.put('key1', 30)
print(round(m.table_load(), 2))
print("\nPDF - table_load example 2")
print("--------------------------")
m = HashMap(53, hash_function_1)
for i in range(50):
m.put('key' + str(i), i * 100)
if i % 10 == 0:
print(round(m.table_load(), 2), m.get_size(), m.get_capacity())
print("\nPDF - clear example 1")
print("---------------------")
m = HashMap(101, hash_function_1)
print(m.get_size(), m.get_capacity())
m.put('key1', 10)
m.put('key2', 20)
m.put('key1', 30)
print(m.get_size(), m.get_capacity())
m.clear()
print(m.get_size(), m.get_capacity())
print("\nPDF - clear example 2")
print("---------------------")
m = HashMap(53, hash_function_1)
print(m.get_size(), m.get_capacity())
m.put('key1', 10)
print(m.get_size(), m.get_capacity())
m.put('key2', 20)
print(m.get_size(), m.get_capacity())
m.resize_table(100)
print(m.get_size(), m.get_capacity())
m.clear()
print(m.get_size(), m.get_capacity())
print("\nPDF - resize example 1")
print("----------------------")
m = HashMap(23, hash_function_1)
m.put('key1', 10)
print(m.get_size(), m.get_capacity(), m.get('key1'), m.contains_key('key1'))
m.resize_table(30)
print(m.get_size(), m.get_capacity(), m.get('key1'), m.contains_key('key1'))
print("\nPDF - resize example 2")
print("----------------------")
m = HashMap(79, hash_function_2)
keys = [i for i in range(1, 1000, 13)]
for key in keys:
m.put(str(key), key * 42)
print(m.get_size(), m.get_capacity())
for capacity in range(111, 1000, 117):
m.resize_table(capacity)
m.put('some key', 'some value')
result = m.contains_key('some key')
m.remove('some key')
for key in keys:
# all inserted keys must be present
result &= m.contains_key(str(key))
# NOT inserted keys must be absent
result &= not m.contains_key(str(key + 1))
print(capacity, result, m.get_size(), m.get_capacity(), round(m.table_load(), 2))
print("\nPDF - get example 1")
print("-------------------")
m = HashMap(31, hash_function_1)
print(m.get('key'))
m.put('key1', 10)
print(m.get('key1'))
print("\nPDF - get example 2")
print("-------------------")
m = HashMap(151, hash_function_2)
for i in range(200, 300, 7):
m.put(str(i), i * 10)
print(m.get_size(), m.get_capacity())
for i in range(200, 300, 21):
print(i, m.get(str(i)), m.get(str(i)) == i * 10)
print(i + 1, m.get(str(i + 1)), m.get(str(i + 1)) == (i + 1) * 10)
print("\nPDF - contains_key example 1")
print("----------------------------")
m = HashMap(53, hash_function_1)
print(m.contains_key('key1'))
m.put('key1', 10)
m.put('key2', 20)
m.put('key3', 30)
print(m.contains_key('key1'))
print(m.contains_key('key4'))
print(m.contains_key('key2'))
print(m.contains_key('key3'))
m.remove('key3')
print(m.contains_key('key3'))
print("\nPDF - contains_key example 2")
print("----------------------------")
m = HashMap(79, hash_function_2)
keys = [i for i in range(1, 1000, 20)]
for key in keys:
m.put(str(key), key * 42)
print(m.get_size(), m.get_capacity())
result = True
for key in keys:
# all inserted keys must be present
result &= m.contains_key(str(key))
# NOT inserted keys must be absent
result &= not m.contains_key(str(key + 1))
print(result)
print("\nPDF - remove example 1")
print("----------------------")
m = HashMap(53, hash_function_1)
print(m.get('key1'))
m.put('key1', 10)
print(m.get('key1'))
m.remove('key1')
print(m.get('key1'))
m.remove('key4')
print("\nPDF - get_keys_and_values example 1")
print("------------------------")
m = HashMap(11, hash_function_2)
for i in range(1, 6):
m.put(str(i), str(i * 10))
print(m.get_keys_and_values())
m.put('20', '200')
m.remove('1')
m.resize_table(2)
print(m.get_keys_and_values())
print("\nPDF - find_mode example 1")
print("-----------------------------")
da = DynamicArray(["apple", "apple", "grape", "melon", "peach"])
mode, frequency = find_mode(da)
print(f"Input: {da}\nMode : {mode}, Frequency: {frequency}")
print("\nPDF - find_mode example 2")
print("-----------------------------")
test_cases = (
["Arch", "Manjaro", "Manjaro", "Mint", "Mint", "Mint", "Ubuntu", "Ubuntu", "Ubuntu"],
["one", "two", "three", "four", "five"],
["2", "4", "2", "6", "8", "4", "1", "3", "4", "5", "7", "3", "3", "2"]
)
for case in test_cases:
da = DynamicArray(case)
mode, frequency = find_mode(da)
print(f"Input: {da}\nMode : {mode}, Frequency: {frequency}\n")