-
Notifications
You must be signed in to change notification settings - Fork 46
/
myHashMap.java
545 lines (469 loc) · 17.7 KB
/
myHashMap.java
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
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
/*
* *** Evan Polk - 002 ***
*
* This hashMap object represents an over simplification of Java's implementation of HashMap within
* Java's Collection Framework Library. You are to complete the following methods:
* - remove(K)
* - replace(K,V)
* - replace(K,V,V)
*
* In addition to the documentation below, you can read the online Java documentation for HashMap for
* the expected behavior / return values of each method below. This object follows the same behavior
* of those methods implemented in this Java library.
*
*/
/**
*
* This sample code is illustrating a hash table using separate chaining. To illustrate this,
* the code is building a Hash Map implementation that emulates Java's HashMap class. This class
* implements many of the java library's class's methods and emulates the behavior of the Map
* interface which is what the Java Library does.
*
* This class is demonstrating the use of separate chaining hashing, which is also used by
* Java's library class. This class is not intended to be a full-blown Hash Map / Hash Table
* implementation, nor does it implement all methods in Java's HashMap class. But the ones that
* are implemented emulate how those methods work in Java's HashMap.
*
* CAVEAT: as indicated, Java provides a HashMap class that is implemented on the Map Interface
* that is more robust, and is more expansive than this implementation. But what is implemented
* operates the same way. This coding example is illustrating sample coding for how hash tables
* using separate chaining (versus open addressing techniques). And the behavior emulates the Map
* interface that Java's HashMap follows.
*
* PUBLIC METHODS:
* ---------------
*
* void clear() - Removes all of the mappings from this map.
* boolean containsValue(V) - Returns true if this map maps one or more keys to the specified value
* boolean containsKey(K) - Returns true if this map contains a mapping for the specified key.
* V get(K) - Returns the value to which the specified key is mapped, or null
* if this map contains no mapping for the key
* V put(K, V) - Associates the specified value with the specified key in this map
* V putIfAbsent(K, V) - If the specified key is not already associated with a value (or
* is mapped to null) associates it with the given value and returns
* null, else returns the current value
* V remove(K) - Removes the entry for the specified key only if it is currently
* mapped to the specified value
* boolean remove(K, V) - Removes the entry for the specified key only if it is currently
* mapped to the specified value.
* boolean replace(K, V) - Replaces the entry for the specified key only if it is currently
* mapped to some value
* V replace(K, V1, V2) - Replaces the entry for the specified key only if currently mapped
* to the specified value.
* Set<K> keySet() - Returns a 'Set' view of the keys contained in the map.
* Set<Map.Entry<K,V>> entrySet() - Returns a 'Set' view of the mappings contains in the map.
* int size() - returns the number of <k,v> pairs in hashmap
* boolean isEmpty() - returns true if this map contains no key-value mappings.
*
*
* Methods *NOT* implemented to fully emulate the behavior
* of Java's HashMap Class
* - clone()
* - compute()
* - computeIfAbsent()
* - computeIfPresent()
* - foreach()
* - merge(()
* - putAll()
* - replaceAll()
* - values()
*
****************************************/
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
/**
* Class HashNode
*
* Node object representing a <Key, Value> pair stored in the Hash Map, elements
* hashed to the same bucket slot will be chained through a singly linked-list.
*/
class HashNode<K,V> {
K key;
V value;
HashNode<K, V> next;
public HashNode() {
this.key=key;
this.value=value;
}
}
/**
* A simple implementation of a HashMap that is built to emulate the Map Interface.
* The <key, values> pairs are stored in a Map, where the key represents a hash
* bucket slot number and the value represents a node which will form as linked-list
* for hash collisions on that bucket's slot.
*
* The array in this class represents the buckets, and each bucket has a pointer
* to a node class for the linked-list of <k,v> pairs. The key for this bucket array
* is generated using a hash function that returns a number from 0 to n-1, where n
* is the number of buckets (array size).
*
* Note: Java provides a HashMap class which implements the HashMap on the Map
* interface. Again, the intent is not to replace it and/or build out to the same
* level. We are illustrating 'separate chaining' using a singly linked-list.
*
* Last, the hashmap (array) is small, in practice, it will be much larger. But this
* will also illustrate the load factor being reached much faster and seeing the hashmap
* growth code be exercised.
*/
class myHashMap<K,V> {
private static final float DEFAULT_LOAD_FACTOR = 0.7f;
private static final int INITIAL_NUM_BUCKETS = 10;
ArrayList<HashNode<K, V>> bucket = new ArrayList<>();
int numBuckets = INITIAL_NUM_BUCKETS;
int size = 0;
public myHashMap() {
for (int i = 0; i < numBuckets; i++) {
bucket.add(null);
}
}
public int Size() { return size; }
public boolean isEmpty() { return size == 0; }
/**
* Method clear()
*
* Reinitialize the hash to INITIAL_NUM_BUCKETS. For each bucket, it resets
* the bucket slots (in the array) to a null Node.
*/
public void clear() {
size = 0;
numBuckets = INITIAL_NUM_BUCKETS;
bucket = new ArrayList<>();
for (int i = 0; i < numBuckets; i++) {
bucket.add(null);
}
}
/**
* method getBucketindex()
*
* Performs two parts.
* 1) First invokes a very simple hash code generator which generates a 32-bit
* integer. The mask (bit operation) masks off the sign bit )turns the
* 32-bit integer into a 31-bit non-negative integer).
* 2) Second, it invokes a compressor expression (in this case, performing a
* MOD operation). This compresses the hash number to between 0 and
* (numBuckets-1) which will be an index into our hash bucket slot (aka,
* key for Map);
*
* @param key - key value to locate hash map bucket for
*
* @return bucketIndex - bucket index number for key value
*/
private int getBucketIndex(K key) {
return (key.hashCode() & 0x7fffffff) % numBuckets;
}
/**
* method: V get(K)
*
* Returns the value to which the specified key is mapped, or null if this map contains
* no mapping for the key. This method will probe to the correct bucket, then loop
* through the bucket's chained nodes (linked-list) until the key is found. If not found,
* the key is not in the hash map and the method will return null.
*
* @param key - key value for identifying the <k,v> pair
*
* @return val - value for the provided key value, else null
*/
public V get(K key) {
int index = getBucketIndex(key);
HashNode<K, V> head = bucket.get(index);
while (head != null) {
if (head.key.equals(key)) {
return head.value;
}
head = head.next;
}
return null;
}
/**
* method: V remove(K)
*
* Removes the entry for the specified key only if it is currently mapped to the
* specified value. The method will probe into the bucket lists. If the bucket
* has a chained list, then it will be traversed to identify the key to
* remove. If no chained list and/or no node is found the chained list representing
* the key, then it is not in the hash map.
*
* If the key is found, it is removed from the chained list and the hashmap size
* is adjusted.
*
* @param key - key value for the <key,value> pair to remove
*
* @return value - return the node for the <key,value>
* removed, else null if not found
*/
public V remove(K key) {
int index = getBucketIndex(key);
HashNode<K, V> cur = bucket.get(index);
HashNode<K, V> prev = null;
while (cur != null) {
if (cur.key.equals(key) && prev == null) {
bucket.set(index, null);
return cur.value;
} else if (cur.key.equals(key)) {
prev.next = prev.next.next;
return cur.value;
}
prev = cur;
cur = cur.next;
}
return null;
}
/**
* Method: boolean remove(K, V)
*
* Removes the entry for the specified key only if it is currently mapped to some value
*
* @param: key - key for identifying <k,v>
* @param: val - will remove <k,v> only if existing value
* equals val
*
* @return: true if deleted, else false
*/
public boolean remove(K key, V val) {
V originalValue = get(key);
if (originalValue == null ||
(! originalValue.equals(val)) ) {
return false;
}
// Key was found and its value equals the passed
// parameter 'val'
remove(key);
return true;
}
/**
* method: V put(K, V)
*
* Associates the specified value with the specified key in this map. The method
* will check if the key is already in the hash map, if so, it updates the value
* and returns the old value. If the key is not found, then it will insert the
* <k,v> pair. Last if inserting the <k,v>, the load factor is checked. If it is
* greater than the DEFAULT_LOAD_FACTOR %, the method will double the bucket
* map and rehash the whole hash map.
*
* @param key - Key to the <k,v> pair operate on
* @param value - if key found, value is updated to this
* param, else routine inserts <k,v>
*
* @return value - if key exists, returns old value before
* replacing with provided value, else null.
*/
public V put(K key, V value) {
/*
* If the <key,value> already exists in the hash map,
* then replace the value, else insert the <key,value>
*/
V oldValue = get(key);
if ( oldValue != null) {
replace(key, value);
return oldValue;
}
int index = getBucketIndex(key);
HashNode<K, V> head = bucket.get(index);
HashNode<K, V> toAdd = new HashNode<>();
toAdd.key = key;
toAdd.value = value;
if (head == null) {
bucket.set(index, toAdd);
size++;
} else {
while (head != null) {
if (head.key.equals(key)) {
head.value = value;
size++;
break;
}
head = head.next;
}
if (head == null) {
head = bucket.get(index);
toAdd.next = head;
bucket.set(index, toAdd);
size++;
}
}
/*
* Check the load factor of the hashmap, if greater
* than DEFAULT_LOAD_FACTOR, we will double the number
* of buckets of our hashmap.
*/
if ((1.0 * size) / numBuckets > DEFAULT_LOAD_FACTOR) {
//do something
ArrayList<HashNode<K, V>> tmp = bucket;
bucket = new ArrayList<>();
numBuckets = 2 * numBuckets;
size = 0;
for (int i = 0; i < numBuckets; i++) {
bucket.add(null);
}
/*
* Traverse the original buckets, and for each bucket
* traverse the nodes stored there (via linked-list).
* For each node (<key, value> pair), add to the new
* (grown) bucket list. The re-add process will
* rehash the keys to the new bucket size.
*/
for (HashNode<K, V> headNode : tmp) {
while (headNode != null) {
put(headNode.key, headNode.value);
headNode = headNode.next;
}
}
}
return null;
}
/**
* method: V putIfAbsent(K, V)
*
* If the specified key is not already associated with a value (or is mapped to null)
* associates it with the given value and returns null, else returns the current value.
*
* @param: key - The key to check if exists in the hashmap
* @parem: value - The value to place in as a <k, v> pair if
* key does not exist
*
* @return: V - returns the existing value if the key is
* found, else null
*/
public V putIfAbsent(K key, V value) {
V originalValue = get(key);
if (originalValue == null ) {
put(key, value);
return null;
}
return originalValue;
}
/**
* method: V replace(K, V)
*
* Replaces the entry for the specified key only if it is currently mapped to some value (aka, the
* key already exist with some value).
*
* @param key - Key for the <k, v> pair to replace its
* value
* @param val - The new value to replace the old one if
* found.
*
* @return V - returns the old value for the <k,v> pair,
* else null if not found.
*/
public V replace(K key, V val) {
/*
* ADD YOUR CODE HERE - DO NOT FORGET TO ADD YOUR NAME AT TOP OF FILE
*
* Make sure you return the proper value based on the outcome of this method's
* replace (see method's prologue above).
*/
int index = getBucketIndex(key);
HashNode<K, V> cur = bucket.get(index);
while (cur != null) {
if (cur.key.equals(key)) {
V oldVal = cur.value;
cur.value = val;
return oldVal;
}
cur = cur.next;
}
return null;
}
/**
* method: boolean replace(K, V, V)
*
* Replaces the entry for the specified key only if currently mapped to the specified value.
*
* @param key - Key for the <k, v> pair to replace its
* value
* @param oldVal - Replace only if current <k,v>'s value
* is same as oldVal
* @param newVal - the new value to use.
*
* @return V - returns the old value for the <k,v> pair,
* else null if not found.
*/
public boolean replace(K key, V oldVal, V newVal) {
/*
* ADD YOUR CODE HERE
*
* This method should apply the precondition (aka, the Key already exists with the
* value 'oldval', and is so, it SHOULD call replace(K, V) for code reuse.
*/
int index = getBucketIndex(key);
HashNode<K, V> cur = bucket.get(index);
while (cur != null) {
if (cur.key.equals(key) && cur.value.equals(oldVal)) {
replace(key, newVal);
return true;
}
cur = cur.next;
}
return false;
}
/**
* Method: boolean contains(V)
*
* Returns true if this map maps one or more keys to the specified value
*
* @param val: Value to search for in hashmap to determine
* if it is contained there.
*
* @return: true if found, else false.
*/
public boolean containsValue(V val) {
for (HashNode<K, V> headNode : bucket) {
while (headNode != null) {
if ( headNode.value.equals(val) )
return true;
headNode = headNode.next;
}
}
return false;
}
/**
* Method: boolean containsKey(K)
*
* Returns true if this map contains a mapping for the specified key.
*
* @param key: The key to search for to determine of hash
* map contains it
*
* @return: true if found, else false.
*/
public boolean containsKey (K key) {
return (get(key) == null ? false : true);
}
/**
* Method: Set<Map.Entry<K,V>> entrySet()
*
* Returns a 'Set' view of the mappings contained in the map.
*
* @return Set<Map.Entry<K,V></K,V>> - set of all K/V pairs in map
*/
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> returnSet = new HashSet<>();
for (HashNode<K, V> headNode : bucket) {
while (headNode != null) {
returnSet.add(Map.entry( headNode.key, headNode.value));
headNode = headNode.next;
}
}
return returnSet;
}
/**
* Method: Set<K> keySet()
*
* Returns a 'Set' view of the keys contained in the map.
*
* @return Set<K> - set of all keys in map
*/
public Set<K> keySet() {
Set<K> returnSet = new HashSet<>();
for (HashNode<K, V> headNode : bucket) {
while (headNode != null) {
returnSet.add(headNode.key);
headNode = headNode.next;
}
}
return returnSet;
}
} /* end class myHashMap */