forked from anthonynsimon/java-ds-algorithms
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathHashTable.java
More file actions
280 lines (233 loc) · 8.72 KB
/
HashTable.java
File metadata and controls
280 lines (233 loc) · 8.72 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
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
package com.anthonynsimon.datastructures;
import com.anthonynsimon.datastructures.util.HashTableNode;
import java.util.ArrayList;
public class HashTable<K, V> {
protected final double maxLoadFactor = 0.75d;
protected final int capacityGrowth = 2;
protected final int initialCapacity;
protected int size;
protected int currentCapacity;
protected ArrayList<HashTableNode<K, V>> buckets;
public HashTable() {
this(16);
}
// Initialize with desired initial number of buckets
public HashTable(int initialCapacity) {
this.initialCapacity = nearestPowerOfTwo(initialCapacity);
resetHashTable();
}
protected static int nearestPowerOfTwo(int value) {
int pow = 0;
while (value >> 1 > 0) {
value >>= 1;
pow++;
}
return 1 << pow;
}
protected void resetHashTable() {
this.size = 0;
this.currentCapacity = this.initialCapacity;
this.buckets = new ArrayList<>(this.initialCapacity);
// Initialize all buckets to null
for (int i = 0; i < this.currentCapacity; i++) {
this.buckets.add(null);
}
}
// Returns the value stored under the given key, if found
public V get(K key) {
HashTableNode<K, V> result = getNodeWithKey(key);
return result != null ? result.getValue() : null;
}
protected void ensureCapacity(int intendedCapacity) {
double loadFactor = (double) intendedCapacity / (double) currentCapacity;
// If we're within the load limit, return early, it's all good.
if (loadFactor < maxLoadFactor) {
return;
}
// Otherwise, ensure we will be within limits
int newCapacity = currentCapacity * capacityGrowth;
buckets.ensureCapacity(newCapacity);
// Initialize buckets
for (int i = this.currentCapacity; i < newCapacity; i++) {
this.buckets.add(null);
}
currentCapacity = newCapacity;
}
// Inserts a Key, Value pair into the table
public void put(K key, V value) throws IllegalArgumentException {
// No support for null as key
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
}
ensureCapacity(size() + 1);
// Hash the key and get the bucket index
int bucketIndex = getBucketIndex(key);
HashTableNode<K, V> newNode = new HashTableNode<>(key, value);
HashTableNode<K, V> current = buckets.get(bucketIndex);
// If bucket is empty, set as first node and we're done
if (current == null) {
buckets.set(bucketIndex, newNode);
this.size++;
return;
}
// Traverse the list within the bucket until match or end found
while (current != null) {
// When a key match is found, replace the value it stores and break
if (current.getKey().equals(key)) {
current.setValue(value);
break;
}
// When the last node of the list is reached, append new node here and break
else if (current.getNext() == null) {
current.setNext(newNode);
this.size++;
break;
}
current = current.getNext();
}
}
// Removes the Key, Value pair based on the provided Key
public void remove(K key) {
if (size() == 0 || key == null) {
return;
}
int bucketIndex = getBucketIndex(key);
HashTableNode<K, V> current = buckets.get(bucketIndex);
HashTableNode<K, V> previous = null;
// Traverse the list inside the bucket until match is found or end of list reached
while (current != null) {
if (current.getKey().equals(key)) {
// Handle case when node is first in bucket
if (previous == null) {
// If there is a next node, set next node as first in bucket
if (current.getNext() != null) {
buckets.set(bucketIndex, current.getNext());
}
// If there is no other node in list, simply set bucket to null
else {
buckets.set(bucketIndex, null);
}
}
// Handle case when node is not in first position
else {
// If it's the last node in the list, set previous's next as null
if (current.getNext() == null) {
previous.setNext(null);
}
// If it's anywhere else in the list, connect previous and next
else {
previous.setNext(current.getNext());
}
}
// We're done removing the node, diminish size and return
this.size--;
return;
}
previous = current;
current = current.getNext();
}
}
// Returns array of all values in table
// Traverse each bucket and add value to results
public V[] values() {
// It is safe to suppress unchecked exception because the array we're creating
// is of the same type as the one passed.
@SuppressWarnings("unchecked")
V[] values = (V[]) new Object[size()];
if (size() > 0) {
int index = 0;
for (int i = 0; i < buckets.size(); i++) {
HashTableNode<K, V> current = buckets.get(i);
while (current != null) {
values[index] = current.getValue();
index++;
current = current.getNext();
}
}
}
return values;
}
// Returns array of all keys in table
// Traverse each bucket and add key to results
public K[] keys() {
// It is safe to suppress unchecked exception because the array we're creating
// is of the same type as the one passed.
@SuppressWarnings("unchecked")
K[] keys = (K[]) new Object[size()];
if (size() > 0) {
int index = 0;
for (int i = 0; i < buckets.size(); i++) {
HashTableNode<K, V> current = buckets.get(i);
while (current != null) {
keys[index] = current.getKey();
index++;
current = current.getNext();
}
}
}
return keys;
}
// Returns if some node in table contains provided key
public boolean containsKey(K key) {
HashTableNode<K, V> result = getNodeWithKey(key);
return result != null;
}
// Returns if some node in table contains provided value
public boolean containsValue(V value) {
HashTableNode<K, V> result = getNodeWithValue(value);
return result != null;
}
// Returns the total number of Key, Value pairs in the table
public int size() {
return this.size;
}
// Empty out the table
public void clear() {
resetHashTable();
}
// Hash the key and find the appropriate bucket index
protected int getBucketIndex(K key) {
// Rehash to protect against poor hash functions
int rehashed = hash(key.hashCode());
// Capacity is always a power of two, use fast modulo operation
return rehashed & (this.currentCapacity - 1);
}
protected int hash(int h) {
h ^= (h >> 20) ^ (h >> 12);
return h ^ (h >> 7) ^ (h >> 4);
}
// Returns the node with the matching key, if any
// Searches only inside the appropriate bucket
private HashTableNode<K, V> getNodeWithKey(K key) {
if (size() == 0 || key == null) {
return null;
}
int bucketIndex = getBucketIndex(key);
HashTableNode<K, V> current = buckets.get(bucketIndex);
while (current != null) {
if (current.getKey().equals(key)) {
return current;
}
current = current.getNext();
}
return null;
}
// Returns the node with the matching value, if any
// Must search the entire table since the value doesn't give us
// a clue about a possible bucket
private HashTableNode<K, V> getNodeWithValue(V value) {
if (size() == 0) {
return null;
}
for (int i = 0; i < buckets.size(); i++) {
HashTableNode<K, V> current = buckets.get(i);
while (current != null) {
if (current.getValue().equals(value)) {
return current;
}
current = current.getNext();
}
}
return null;
}
}