libdict 1.0.7
Data Structure C Library
Loading...
Searching...
No Matches
hashtable.c
Go to the documentation of this file.
1/*
2 * libdict -- chained hash-table, with chains sorted by hash, implementation.
3 *
4 * Copyright (c) 2001-2014, Farooq Mela
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28/*
29 * cf. [Gonnet 1984], [Knuth 1998]
30 */
31
32#include "hashtable.h"
33
34#include <string.h> /* For memset() */
35#include "dict_private.h"
36#include "hashtable_common.h"
37
38/* TODO: make this configurable in the constructor methods */
39#define LOADFACTOR_NUMERATOR 2
40#define LOADFACTOR_DENOMINATOR 3
41#if LOADFACTOR_NUMERATOR > LOADFACTOR_DENOMINATOR
42# error LOADFACTOR_NUMERATOR must be less than LOADFACTOR_DENOMINATOR
43#endif
44
45typedef struct hash_node hash_node;
46
47struct hash_node {
48 void* key;
49 void* datum;
51
52 /* Only because iterators are bidirectional: */
54 unsigned hash; /* Untruncated hash value. */
55};
56
57struct hashtable {
61 size_t count;
62 unsigned size;
63 /* Bumped by every change that invalidates iterators: a resize, which
64 * rehashes every node into a different slot, and a clear. Iterators
65 * record it when positioned and treat a mismatch as unpositioned. */
66 size_t gen;
67};
68
72 unsigned slot;
73 size_t gen;
74};
75
76/* Vtable thunks. The vtable slot types take void*, and calling a function
77 * through a function-pointer type that is not compatible with the function's
78 * own type is undefined behaviour (C11 6.5.2.2p9), so each slot below gets a
79 * thunk with the exact slot signature instead of a cast. A cast would also
80 * hide a genuine signature mismatch. */
81static dict_itor* vt_hashtable_dict_itor_new(void* obj) { return hashtable_dict_itor_new(obj); }
82static size_t vt_hashtable_free(void* obj, dict_delete_func delete_func) { return hashtable_free(obj, delete_func); }
83static dict_insert_result vt_hashtable_insert(void* obj, void* key) { return hashtable_insert(obj, key); }
84static void** vt_hashtable_search(void* obj, const void* key) { return hashtable_search(obj, key); }
85static dict_remove_result vt_hashtable_remove(void* obj, const void* key) { return hashtable_remove(obj, key); }
86static size_t vt_hashtable_clear(void* obj, dict_delete_func delete_func) { return hashtable_clear(obj, delete_func); }
87static size_t vt_hashtable_traverse(void* obj, dict_visit_func visit, void* user_data) { return hashtable_traverse(obj, visit, user_data); }
88static size_t vt_hashtable_count(const void* obj) { return hashtable_count(obj); }
89static bool vt_hashtable_verify(const void* obj) { return hashtable_verify(obj); }
90static void vt_hashtable_itor_free(void* itor) { hashtable_itor_free(itor); }
91static bool vt_hashtable_itor_valid(const void* itor) { return hashtable_itor_valid(itor); }
92static void vt_hashtable_itor_invalidate(void* itor) { hashtable_itor_invalidate(itor); }
93static bool vt_hashtable_itor_next(void* itor) { return hashtable_itor_next(itor); }
94static bool vt_hashtable_itor_prev(void* itor) { return hashtable_itor_prev(itor); }
95static bool vt_hashtable_itor_nextn(void* itor, size_t count) { return hashtable_itor_nextn(itor, count); }
96static bool vt_hashtable_itor_prevn(void* itor, size_t count) { return hashtable_itor_prevn(itor, count); }
97static bool vt_hashtable_itor_first(void* itor) { return hashtable_itor_first(itor); }
98static bool vt_hashtable_itor_last(void* itor) { return hashtable_itor_last(itor); }
99static void* vt_hashtable_itor_key(void* itor) { return dict_unconst(hashtable_itor_key(itor)); }
100static void** vt_hashtable_itor_datum(void* itor) { return hashtable_itor_datum(itor); }
101static bool vt_hashtable_itor_search(void* itor, const void* key) { return hashtable_itor_search(itor, key); }
102static bool vt_hashtable_itor_remove(void* itor) { return hashtable_itor_remove(itor); }
103
104static const dict_vtable hashtable_vtable = {
105 false,
106 vt_hashtable_dict_itor_new,
107 vt_hashtable_free,
108 vt_hashtable_insert,
109 vt_hashtable_search,
110 NULL,/* search_le: not supported */
111 NULL,/* search_lt: not supported */
112 NULL,/* search_ge: not supported */
113 NULL,/* search_gt: not supported */
114 vt_hashtable_remove,
115 vt_hashtable_clear,
116 vt_hashtable_traverse,
117 NULL,
118 vt_hashtable_count,
119 vt_hashtable_verify,
120};
121
122static const itor_vtable hashtable_itor_vtable = {
123 vt_hashtable_itor_free,
124 vt_hashtable_itor_valid,
125 vt_hashtable_itor_invalidate,
126 vt_hashtable_itor_next,
127 vt_hashtable_itor_prev,
128 vt_hashtable_itor_nextn,
129 vt_hashtable_itor_prevn,
130 vt_hashtable_itor_first,
131 vt_hashtable_itor_last,
132 vt_hashtable_itor_key,
133 vt_hashtable_itor_datum,
134 vt_hashtable_itor_search,
135 NULL,/* itor_search_le: not supported */
136 NULL,/* itor_search_lt: not supported */
137 NULL,/* itor_search_ge: not supported */
138 NULL,/* itor_search_gt: not supported */
139 vt_hashtable_itor_remove,
140 NULL,/* hashtable_itor_compare not implemented yet */
141};
142
144hashtable_new(dict_compare_func cmp_func, dict_hash_func hash_func, unsigned size)
145{
146 ASSERT(cmp_func != NULL);
147 ASSERT(hash_func != NULL);
148 ASSERT(size > 0);
149
150 hashtable* table = MALLOC(sizeof(*table));
151 if (table) {
152 table->size = dict_prime_geq(size);
153 table->table = dict_malloc_array(table->size, sizeof(hash_node*));
154 if (!table->table) {
155 FREE(table);
156 return NULL;
157 }
158 memset(table->table, 0, table->size * sizeof(hash_node*));
159 table->cmp_func = cmp_func;
160 table->hash_func = hash_func;
161 table->count = 0;
162 table->gen = 0;
163 }
164 return table;
165}
166
167dict*
168hashtable_dict_new(dict_compare_func cmp_func, dict_hash_func hash_func, unsigned size)
169{
170 ASSERT(cmp_func != NULL);
171 ASSERT(hash_func != NULL);
172 ASSERT(size > 0);
173
174 dict* dct = MALLOC(sizeof(*dct));
175 if (dct) {
176 dct->_object = hashtable_new(cmp_func, hash_func, size);
177 if (!dct->_object) {
178 FREE(dct);
179 return NULL;
180 }
181 dct->_vtable = &hashtable_vtable;
182 }
183 return dct;
184}
185
186size_t
188{
189 ASSERT(table != NULL);
190
191 size_t count = hashtable_clear(table, delete_func);
192 FREE(table->table);
193 FREE(table);
194 return count;
195}
196
198hashtable_insert(hashtable* table, void* key)
199{
200 if (LOADFACTOR_DENOMINATOR * table->count >= LOADFACTOR_NUMERATOR * table->size) {
201 /* Load factor too high. A resize invalidates iterators, so do not
202 * resize for a key that is already present: nothing is added. */
203 void** datum_ptr = hashtable_search(table, key);
204 if (datum_ptr)
205 return (dict_insert_result) { datum_ptr, false };
206 hashtable_resize(table, table->size + 1);
207 }
208
209 const unsigned hash = table->hash_func(key);
210 const unsigned mhash = hash % table->size;
211 hash_node* node = table->table[mhash];
212 hash_node* prev = NULL;
213 while (node && hash >= node->hash) {
214 if (hash == node->hash && table->cmp_func(key, node->key) == 0)
215 return (dict_insert_result) { &node->datum, false };
216 prev = node;
217 node = node->next;
218 }
219
220 hash_node* add = MALLOC(sizeof(*add));
221 if (!add)
222 return (dict_insert_result) { NULL, false };
223
224 add->key = key;
225 add->datum = NULL;
226 add->hash = hash;
227 add->prev = prev;
228
229 if (prev)
230 prev->next = add;
231 else
232 table->table[mhash] = add;
233
234 add->next = node;
235 if (node)
236 node->prev = add;
237
238 table->count++;
239 return (dict_insert_result) { &add->datum, true };
240}
241
242void**
243hashtable_search(hashtable* table, const void* key)
244{
245 const unsigned hash = table->hash_func(key);
246 hash_node* node = table->table[hash % table->size];
247 while (node && hash >= node->hash) {
248 if (hash == node->hash && table->cmp_func(key, node->key) == 0)
249 return &node->datum;
250
251 node = node->next;
252 }
253 return NULL;
254}
255
256static void
257remove_node(hashtable* table, hash_node* node, unsigned mhash)
258{
259 if (node->prev)
260 node->prev->next = node->next;
261 else
262 table->table[mhash] = node->next;
263
264 if (node->next)
265 node->next->prev = node->prev;
266
267 FREE(node);
268 table->count--;
269}
270
272hashtable_remove(hashtable* table, const void* key)
273{
274 const unsigned hash = table->hash_func(key);
275 const unsigned mhash = hash % table->size;
276
277 hash_node* node = table->table[mhash];
278 while (node && hash >= node->hash) {
279 if (hash == node->hash && table->cmp_func(key, node->key) == 0) {
280 dict_remove_result result = { node->key, node->datum, true };
281 remove_node(table, node, mhash);
282 return result;
283 }
284 node = node->next;
285 }
286 return (dict_remove_result) { NULL, NULL, false };
287}
288
289size_t
291{
292 for (unsigned slot = 0; slot < table->size; slot++) {
293 hash_node* node = table->table[slot];
294 while (node != NULL) {
295 hash_node* next = node->next;
296 if (delete_func) delete_func(node->key, node->datum);
297 FREE(node);
298 node = next;
299 }
300 table->table[slot] = NULL;
301 }
302
303 const size_t count = table->count;
304 table->count = 0;
305 table->gen++;
306 return count;
307}
308
309size_t
310hashtable_traverse(hashtable* table, dict_visit_func visit, void* user_data)
311{
312 size_t count = 0;
313 for (unsigned i = 0; i < table->size; i++) {
314 for (hash_node* node = table->table[i]; node; node = node->next) {
315 ++count;
316 if (!visit(node->key, node->datum, user_data)) return count;
317 }
318 }
319 return count;
320}
321
322size_t
324{
325 return table->count;
326}
327
328size_t
330{
331 return table->size;
332}
333
334size_t
336{
337 size_t count = 0;
338 for (unsigned i = 0; i < table->size; i++)
339 if (table->table[i])
340 count++;
341 return count;
342}
343
344bool
345hashtable_resize(hashtable* table, unsigned new_size)
346{
347 ASSERT(new_size > 0);
348
349 new_size = dict_prime_geq(new_size);
350 if (table->size == new_size) return true;
351
352 /* TODO: investigate whether using realloc would be advantageous. */
353 hash_node** ntable = dict_malloc_array(new_size, sizeof(hash_node*));
354 if (!ntable) return false;
355
356 memset(ntable, 0, new_size * sizeof(hash_node*));
357
358 for (unsigned i = 0; i < table->size; i++) {
359 for (hash_node* node = table->table[i]; node;) {
360 hash_node* const next = node->next;
361 const unsigned mhash = node->hash % new_size;
362 hash_node* search = ntable[mhash];
363 hash_node* prev = NULL;
364 while (search && node->hash >= search->hash) {
365 prev = search;
366 search = search->next;
367 }
368 if ((node->next = search) != NULL)
369 search->prev = node;
370 if ((node->prev = prev) != NULL)
371 prev->next = node;
372 else
373 ntable[mhash] = node;
374 node = next;
375 }
376 }
377
378 FREE(table->table);
379 table->table = ntable;
380 table->size = new_size;
381 table->gen++;
382 return true;
383}
384
385bool
387{
388 for (unsigned slot = 0; slot < table->size; ++slot) {
389 for (hash_node* n = table->table[slot]; n; n = n->next) {
390 if (n == table->table[slot]) {
391 VERIFY(n->prev == NULL);
392 } else {
393 VERIFY(n->prev != NULL);
394 VERIFY(n->prev->next == n);
395 }
396 if (n->next) {
397 VERIFY(n->next->prev == n);
398 VERIFY(n->hash <= n->next->hash);
399 }
400 VERIFY(n->hash % table->size == slot);
401 }
402 }
403 return true;
404}
405
408{
409 hashtable_itor* itor = MALLOC(sizeof(*itor));
410 if (itor) {
411 itor->table = table;
412 itor->node = NULL;
413 itor->slot = 0;
414 itor->gen = table->gen;
415 }
416 return itor;
417}
418
421{
422 dict_itor* itor = MALLOC(sizeof(*itor));
423 if (itor) {
424 if (!(itor->_itor = hashtable_itor_new(table))) {
425 FREE(itor);
426 return NULL;
427 }
428 itor->_vtable = &hashtable_itor_vtable;
429 }
430 return itor;
431}
432
433void
435{
436 FREE(itor);
437}
438
439/* True if the table was resized or cleared since |itor| was positioned, in
440 * which case its node and slot must not be used. */
441static inline bool
442itor_stale(const hashtable_itor* itor)
443{
444 return itor->gen != itor->table->gen;
445}
446
447/* Unpositions |itor| if it is stale, and returns whether it is positioned. */
448static inline bool
449itor_check(hashtable_itor* itor)
450{
451 if (itor->node && itor_stale(itor)) {
452 itor->node = NULL;
453 itor->slot = 0;
454 }
455 return itor->node != NULL;
456}
457
458bool
460{
461 return itor->node != NULL && !itor_stale(itor);
462}
463
464void
466{
467 itor->node = NULL;
468 itor->slot = 0;
469}
470
471bool
473{
474 if (!itor_check(itor))
475 return false;
476
477 if ((itor->node = itor->node->next) != NULL)
478 return true;
479
480 unsigned slot = itor->slot;
481 while (++slot < itor->table->size) {
482 if (itor->table->table[slot]) {
483 itor->node = itor->table->table[slot];
484 itor->slot = slot;
485 return true;
486 }
487 }
488 itor->node = NULL;
489 itor->slot = 0;
490 return false;
491}
492
493bool
495{
496 if (!itor_check(itor))
497 return false;
498
499 if ((itor->node = itor->node->prev) != NULL)
500 return true;
501
502 unsigned slot = itor->slot;
503 while (slot > 0) {
504 hash_node* node = itor->table->table[--slot];
505 if (node) {
506 while (node->next)
507 node = node->next;
508 itor->node = node;
509 itor->slot = slot;
510 return true;
511 }
512 }
513 itor->node = NULL;
514 itor->slot = 0;
515 return false;
516}
517
518bool
520{
521 while (count--) {
522 if (!hashtable_itor_next(itor))
523 return false;
524 }
525 return itor_check(itor);
526}
527
528bool
530{
531 while (count--) {
532 if (!hashtable_itor_prev(itor))
533 return false;
534 }
535 return itor_check(itor);
536}
537
538bool
540{
541 itor->gen = itor->table->gen;
542 for (unsigned slot = 0; slot < itor->table->size; ++slot) {
543 if (itor->table->table[slot]) {
544 itor->node = itor->table->table[slot];
545 itor->slot = slot;
546 return true;
547 }
548 }
549 itor->node = NULL;
550 itor->slot = 0;
551 return false;
552}
553
554bool
556{
557 itor->gen = itor->table->gen;
558 for (unsigned slot = itor->table->size; slot > 0;) {
559 if (itor->table->table[--slot]) {
560 hash_node* node = itor->table->table[slot];
561 while (node->next)
562 node = node->next;
563 itor->node = node;
564 itor->slot = slot;
565 return true;
566 }
567 }
568 itor->node = NULL;
569 itor->slot = 0;
570 return false;
571}
572
573bool
575{
576 itor->gen = itor->table->gen;
577 const unsigned hash = itor->table->hash_func(key);
578 const unsigned mhash = hash % itor->table->size;
579 hash_node* node = itor->table->table[mhash];
580 while (node && hash >= node->hash) {
581 if (hash == node->hash && itor->table->cmp_func(key, node->key) == 0) {
582 itor->node = node;
583 itor->slot = mhash;
584 return true;
585 }
586 node = node->next;
587 }
588 itor->node = NULL;
589 itor->slot = 0;
590 return false;
591}
592
593const void*
595{
596 return hashtable_itor_valid(itor) ? itor->node->key : NULL;
597}
598
599void**
601{
602 return itor_check(itor) ? &itor->node->datum : NULL;
603}
604
605bool
607{
608 if (!itor_check(itor))
609 return false;
610 remove_node(itor->table, itor->node, itor->node->hash % itor->table->size);
611 itor->node = NULL;
612 return true;
613}
int(* dict_compare_func)(const void *, const void *)
A pointer to a function that compares two keys.
Definition dict.h:59
void(* dict_delete_func)(void *, void *)
A pointer to a function that is called when a key-value pair gets removed from a dictionary.
Definition dict.h:62
unsigned(* dict_hash_func)(const void *)
A pointer to a function that returns the hash value of a key.
Definition dict.h:66
bool(* dict_visit_func)(const void *, void *, void *)
A pointer to a function used for iterating over dictionary contents.
Definition dict.h:64
#define FREE(p)
#define ASSERT(expr)
#define VERIFY(expr)
#define MALLOC(n)
size_t hashtable_free(hashtable *table, dict_delete_func delete_func)
Remove every element, calling |delete_func|(key, datum) for each unless |delete_func| is NULL,...
Definition hashtable.c:187
#define LOADFACTOR_NUMERATOR
Definition hashtable.c:39
bool hashtable_itor_search(hashtable_itor *itor, const void *key)
Position |itor| on |key| and return true, or unposition it and return false if |key| is not present.
Definition hashtable.c:574
dict_remove_result hashtable_remove(hashtable *table, const void *key)
Remove the element with key |key|.
Definition hashtable.c:272
dict_itor * hashtable_dict_itor_new(hashtable *table)
Like hashtable_itor_new(), but wrapped in a generic dict_itor for use with the dict_itor_* macros.
Definition hashtable.c:420
hashtable_itor * hashtable_itor_new(hashtable *table)
Create an iterator over |table| that is not positioned on any element yet.
Definition hashtable.c:407
bool hashtable_verify(const hashtable *table)
Check the internal invariants of the table and return true if they hold, or print the first failed ch...
Definition hashtable.c:386
bool hashtable_itor_last(hashtable_itor *itor)
Position |itor| on the last element in table order (unspecified) and return true, or unposition it an...
Definition hashtable.c:555
bool hashtable_itor_next(hashtable_itor *itor)
Move |itor| to the next element in table order (unspecified) and return true, or unposition it and re...
Definition hashtable.c:472
bool hashtable_itor_first(hashtable_itor *itor)
Position |itor| on the first element in table order (unspecified) and return true,...
Definition hashtable.c:539
size_t hashtable_count(const hashtable *table)
Return the number of elements.
Definition hashtable.c:323
bool hashtable_itor_prevn(hashtable_itor *itor, size_t count)
Move |itor| |count| elements back and return whether it is still positioned.
Definition hashtable.c:529
#define LOADFACTOR_DENOMINATOR
Definition hashtable.c:40
dict_insert_result hashtable_insert(hashtable *table, void *key)
Insert |key| unless an equal key is present.
Definition hashtable.c:198
void ** hashtable_itor_datum(hashtable_itor *itor)
Return the address of the datum |itor| is positioned on, through which it can be read or replaced,...
Definition hashtable.c:600
bool hashtable_itor_remove(hashtable_itor *itor)
Remove the element |itor| is positioned on and unposition |itor|; return false if it was not position...
Definition hashtable.c:606
void hashtable_itor_free(hashtable_itor *itor)
Free an iterator.
Definition hashtable.c:434
bool hashtable_resize(hashtable *table, unsigned new_size)
Rehash the table into at least |size| slots, rounded up as in hashtable_new().
Definition hashtable.c:345
bool hashtable_itor_nextn(hashtable_itor *itor, size_t count)
Move |itor| |count| elements forward and return whether it is still positioned.
Definition hashtable.c:519
size_t hashtable_clear(hashtable *table, dict_delete_func delete_func)
Remove every element, calling |delete_func|(key, datum) for each unless |delete_func| is NULL.
Definition hashtable.c:290
bool hashtable_itor_prev(hashtable_itor *itor)
Move |itor| to the previous element in table order (unspecified) and return true, or unposition it an...
Definition hashtable.c:494
void hashtable_itor_invalidate(hashtable_itor *itor)
Unposition |itor|, so that it is not positioned on any element.
Definition hashtable.c:465
size_t hashtable_traverse(hashtable *table, dict_visit_func visit, void *user_data)
Call |visit|(key, datum, |user_data|) for each element in table order (unspecified),...
Definition hashtable.c:310
dict * hashtable_dict_new(dict_compare_func cmp_func, dict_hash_func hash_func, unsigned size)
Like hashtable_new(), but wrapped in a generic dict for use with the dict_* macros.
Definition hashtable.c:168
bool hashtable_itor_valid(const hashtable_itor *itor)
Return whether |itor| is positioned on an element.
Definition hashtable.c:459
void ** hashtable_search(hashtable *table, const void *key)
Return the address of the datum stored for |key|, or NULL if |key| is not present.
Definition hashtable.c:243
size_t hashtable_size(const hashtable *table)
Return the current number of slots in the table.
Definition hashtable.c:329
size_t hashtable_slots_used(const hashtable *table)
Return the number of slots whose chain is not empty.
Definition hashtable.c:335
const void * hashtable_itor_key(const hashtable_itor *itor)
Return the key |itor| is positioned on, or NULL if it is not positioned.
Definition hashtable.c:594
hashtable * hashtable_new(dict_compare_func cmp_func, dict_hash_func hash_func, unsigned size)
Create an empty hash table that compares keys with |cmp_func| and hashes them with |hash_func| (neith...
Definition hashtable.c:144
Hash table with separate chaining; each chain is kept sorted by hash value.
unsigned dict_prime_geq(unsigned n)
void * _itor
Definition dict.h:235
const itor_vtable * _vtable
Definition dict.h:236
The operations of one container type.
Definition dict.h:110
Definition dict.h:164
void * _object
Definition dict.h:165
const dict_vtable * _vtable
Definition dict.h:166
unsigned hash
Definition hashtable.c:54
void * key
Definition hashtable.c:48
hash_node * next
Definition hashtable.c:50
hash_node * prev
Definition hashtable.c:53
void * datum
Definition hashtable.c:49
unsigned slot
Definition hashtable.c:72
hashtable * table
Definition hashtable.c:70
hash_node * node
Definition hashtable.c:71
unsigned size
Definition hashtable.c:62
dict_compare_func cmp_func
Definition hashtable.c:59
size_t gen
Definition hashtable.c:66
size_t count
Definition hashtable.c:61
hash_node ** table
Definition hashtable.c:58
dict_hash_func hash_func
Definition hashtable.c:60