-
Notifications
You must be signed in to change notification settings - Fork 0
/
hashtable.c
332 lines (263 loc) · 8.81 KB
/
hashtable.c
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
#include "hashtable.h"
/* MAKE SURE TO VALIDATE KEYS--NO EMPTY STRINGS */
/* TODO: for hash2, see if there's a way to involve all
characters. Maybe alternate adding and subtracting each ASCII
value from a sum and finding the magnitude (to replace
the key[0] term)? */
/* Returns a hash table index based on the
key given. Used for initial hashing. Uses
Bob Jenkins's one-at-a-time hash, found here:
en.wikipedia.org/wiki/Jenkins_hash_function */
SIZEINT hash1(char *key, SIZEINT tablesize) {
KEYLENINT keylen;
KEYLENINT i = 0;
OAATJENINT hash = 0;
keylen = strlen(key);
while (i != keylen) {
hash += key[i++];
hash += hash << 10;
hash ^= hash >> 6;
}
hash += hash << 3;
hash ^= hash >> 11;
hash += hash << 15;
hash = hash % tablesize;
return hash;
}
/* Second hash function. Modified from
slide 24 from the source below:
courses.cs.washington.edu/courses/cse326/00wi/handouts/lecture16/sld024.htm */
SIZEINT hash2(char *key, SIZEINT tablesize) {
KEYLENINT keylen;
KEYLENINT i;
uint8_t ascii_term = 0;
SIZEINT prime;
keylen = strlen(key);
prime = prevprime(tablesize);
for(i = 0; i < keylen; i++) {
if(i % 2)
ascii_term += (uint8_t)(key[i]);
else
ascii_term -= (uint8_t)(key[i]);
}
return (prime - (keylen * ascii_term) % prime);
}
/* Makes an empty dynamically-allocated hash table
with a size of the next prime number above the
requested size. Returns NULL if the requested size
is too large OR if the inputted size is 0. */
HashTable *htcreate(SIZEINT reqsize) {
HashTable *ht = NULL;
SIZEINT truesize;
truesize = nextprime(reqsize);
if(truesize < 0 || reqsize <= 0)
/* If nextprime returned 0 (meaning that
the prime number requested is too large
for a SIZEINT integer), return NULL to
indicate failure. */
/* Also return NULL if a table with
an invalid size was requested. */
return NULL;
ht = (HashTable*)malloc(sizeof(HashTable));
ht->size = truesize;
ht->keys = (char**)calloc(truesize, sizeof(char*));
ht->values = (void**)malloc(sizeof(void*) * truesize);
ht->entries = 0;
return ht;
}
/* Places a value into the hash table. Returns 1 on success
and 0 otherwise. */
uint8_t htinsert(HashTable *ht, char *key, void *value) {
SIZEINT index, rehashstep;
index = htcontains(ht, key);
/* Case in which the same key was entered again;
simply overwrites the value belonging to the key
with the new value. */
if(index > 0) {
ht->values[index] = value;
return 1;
}
/* Grows the hash table by a factor of GF if the load
factor is greater than CRITLF */
if(((float)(ht->entries + 1) / ht->size) > CRITLF)
if(!htgrow(ht))
/* Return 0 to indicate failure if
htgrow() fails. */
return 0;
index = hash1(key, ht->size);
/* This wrapping if condition prevents running hash2 unnecessarily */
if(ht->keys[index] != NULL) {
rehashstep = hash2(key, ht->size);
while(ht->keys[index] != NULL) {
/* A free space in the table is guaranteed;
the load factor was constrained to be less than CRITLF,
which must be less than 1. */
index = (index + rehashstep) % ht->size;
}
}
ht->keys[index] = key;
ht->values[index] = value;
ht->entries++;
return 1;
}
/* Searches the table for an entry with the given
key. Returns the index of the key if found or
returns -1. */
SIZEINT htcontains(HashTable *ht, char *key) {
SIZEINT attempt, rehashstep, initialattempt;
attempt = hash1(key, ht->size);
/* If the key is at the first index attempt, immediately return that index.
The Boolean condition is prepended by a null check */
if(ht->keys[attempt] != NULL && !strcmp(ht->keys[attempt], key))
return attempt;
/* Record the initial attempt and probe until the key is
found or the initial attempt is reencountered */
initialattempt = attempt;
rehashstep = hash2(key, ht->size);
while(1) {
attempt = (attempt + rehashstep) % ht->size;
/* Case in which the key is found (with the Boolean
condition prepended by a null check). */
if(ht->keys[attempt] != NULL && !strcmp(ht->keys[attempt], key))
return attempt;
/* Case in which every hash table value was visited and
probing returned to the initial-attempt index */
if(attempt == initialattempt)
return -1;
}
}
/* Gets the value associated with a particular
key from the hash table. Returns NULL if the
key could not be found.*/
void *htget(HashTable *ht, char *key) {
SIZEINT index;
/* If the hash table does not contain the inputted
key, return NULL. */
if((index = htcontains(ht, key)) < 0)
return NULL;
return ht->values[index];
}
/* Removes the entry with the given key. Returns the pointer
stored in the table with that key on success (to allow for
quick deallocation by the user) and NULL otherwise. */
void *htremove(HashTable *ht, char *key) {
SIZEINT index;
void *ptr;
if((index = htcontains(ht, key)) < 0)
return NULL;
ptr = ht->values[index];
ht->keys[index] = 0;
/* Note: clearing the value that corresponds to the key
is a waste of time and resources. */
ht->entries--;
return ptr;
}
/* Deallocates all dynamically-allocated memory associated with the
hash table that is not the user's responsiblity. */
void htdestroy(HashTable *ht) {
free(ht->keys);
free(ht->values);
free(ht);
}
/* Expands the hash table by a factor defined by GF. Returns
1 on success; returns 0 otherwise. */
uint8_t htgrow(HashTable *ht) {
SIZEINT i, newsize;
char **oldkeys;
void **oldvalues;
oldkeys = ht->keys;
oldvalues = ht->values;
newsize = nextprime((SIZEINT)((ht->size) * GF + 1));
/* If nextprime returned -1 (meaning that
the prime number requested is too large
for a a SIZEINT integer), return 0 to indicate
failure. */
if(newsize < 0)
return 0;
ht->keys = (char**)calloc(newsize, sizeof(char*));
ht->values = (void**)malloc(sizeof(void*) * newsize);
/* If malloc could not find free space (meaning
it returned NULL) for either the keys or the
values, abort the creation of new key and value
arrays and return 0 to indicate failure. */
if(ht->keys == NULL || ht->values == NULL) {
ht->keys = oldkeys;
ht->values = oldvalues;
return 0;
}
ht->entries = 0;
for(i = 0; i < ht->size; i++) {
if(oldkeys[i] != NULL) {
/* htinsert() will always succeed here; the new load factor
is guaranteed to be less than CRITLF and there are certainly
free spaces in the hash table. */
htinsert(ht, oldkeys[i], oldvalues[i]);
}
}
ht->size = newsize;
free(oldkeys);
free(oldvalues);
return 1;
}
/* A method used primarily for development,
testing, etc. that prints a string representation
of the hash table to the terminal. */
void htprint(HashTable *ht) {
int i;
printf("Entries: %d\n", ht->entries);
printf("Size: %d\n", ht->size);
printf("Load factor: %f\n", (float)(ht->entries) / ht->size);
for(i = 0; i < ht->size; i++) {
if(ht->keys[i] != NULL)
printf("%s: %d\n", ht->keys[i], *((int*)ht->values[i]));
else
printf("Empty spot\n");
}
}
/* Finds the next prime number above the
given number. Returns -1 if the prime number
requested leads to an overflow.
geeksforgeeks.org/program-to-find-the-next-prime-number/ */
SIZEINT nextprime(SIZEINT n) {
if (n > MAXPRIME)
/* If n is greater than the largest prime number
representable by a SIZEINT, return -1 to indicate
an overflow issue. */
return -1;
while(1) {
if(isprime(n))
return n;
n++;
}
}
/* Finds the previous prime number below the
given number. Returns -1 if there is no
previous prime number.
geeksforgeeks.org/program-to-find-the-next-prime-number/ */
SIZEINT prevprime(SIZEINT n) {
if (n < 2)
/* If n is greater than the largest prime number
representable by a SIZEINT, return 0 to indicate
an overflow issue. */
return -1;
while(1) {
n--;
if(isprime(n))
return n;
}
}
/* Returns 1 if the given integer is prime and 0 otherwise.
geeksforgeeks.org/program-to-find-the-next-prime-number/ */
uint8_t isprime(SIZEINT n) {
int i;
if (n <= 1)
return 0;
if (n <= 3)
return 1;
if (n % 2 == 0 || n % 3 == 0)
return 0;
for(i = 5; i*i<=n; i=i+6)
if(n % i == 0 || n % (i+2) == 0)
return 0;
return 1;
}