1 /*  The MIT License
2 
3     Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
4     Copyright (c) 2019 James S Blachly, MD <james.blachly@gmail.com>
5 
6     Permission is hereby granted, free of charge, to any person obtaining
7     a copy of this software and associated documentation files (the
8     "Software"), to deal in the Software without restriction, including
9     without limitation the rights to use, copy, modify, merge, publish,
10     distribute, sublicense, and/or sell copies of the Software, and to
11     permit persons to whom the Software is furnished to do so, subject to
12     the following conditions:
13 
14     The above copyright notice and this permission notice shall be
15     included in all copies or substantial portions of the Software.
16 
17     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
21     BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
22     ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23     CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24     SOFTWARE.
25 */
26 
27 module dklib.khash;
28 
29 import std.traits : isNumeric, isSomeString, isSigned;
30 import core.stdc.stdint;    // uint32_t, etc.
31 import core.memory;         // GC
32 
33 /*!
34   @header
35 
36   Generic hash table library.
37  */
38 
39 enum AC_VERSION_KHASH_H = "0.2.8";
40 
41 import core.stdc.stdlib;
42 import core.stdc..string;
43 import core.stdc.limits;
44 
45 /* compiler specific configuration */
46 
47 alias khint32_t = uint;
48 
49 alias khint64_t = ulong;
50 
51 alias khint_t = khint32_t;
52 alias khiter_t = khint_t;
53 
54 pragma(inline, true)
55 {
56     /// bucket empty?
57     auto __ac_isempty(T)(const(khint32_t)* flag, T i)
58     {
59         return ((flag[i >> 4] >> ((i & 0xfU) << 1)) & 2);
60     }
61 
62     /// bucket deleted?
63     auto __ac_isdel(T)(const(khint32_t)* flag, T i)
64     {
65         return ((flag[i >> 4] >> ((i & 0xfU) << 1)) & 1);
66     }
67 
68     /// bucket empty OR deleted?
69     auto __ac_iseither(T)(const(khint32_t)* flag, T i)
70     {
71         return ((flag[i >> 4] >> ((i & 0xfU) << 1)) & 3);
72     }
73 
74     /// unmark deleted
75     void __ac_set_isdel_false(T)(khint32_t* flag, T i)
76     {
77         flag[i >> 4] &= ~(1uL << ((i & 0xfU) << 1));
78     }
79 
80     /// unmark empty
81     void __ac_set_isempty_false(T)(khint32_t* flag, T i)
82     {
83         flag[i >> 4] &= ~(2uL << ((i & 0xfU) << 1));
84     }
85 
86     /// mark neither empty nor deleted
87     void __ac_set_isboth_false(T)(khint32_t* flag, T i)
88     {
89         flag[i >> 4] &= ~(3uL << ((i & 0xfU) << 1));
90     }
91 
92     /// mark deleted
93     void __ac_set_isdel_true(T)(khint32_t* flag, T i)
94     {
95         flag[i >> 4] |= 1uL << ((i & 0xfU) << 1);
96     }
97 
98     auto __ac_fsize(T)(T m)
99     {
100         return ((m) < 16 ? 1 : (m) >> 4);
101     }
102 
103     void kroundup32(T)(ref T x)
104     {
105         (--(x), (x) |= (x) >> 1, (x) |= (x) >> 2, (x) |= (x) >> 4,
106             (x) |= (x) >> 8, (x) |= (x) >> 16, ++(x));
107     }
108 }
109 
110 alias kcalloc = calloc;
111 
112 alias kmalloc = malloc;
113 
114 alias krealloc = realloc;
115 
116 alias kfree = free;
117 
118 private const double __ac_HASH_UPPER = 0.77;
119 
120 /// Straight port of khash's generic C approach
121 template khash(KT, VT, bool kh_is_map = true, bool useGC = true)
122 {
123     static assert(!isSigned!KT, "Numeric key types must be unsigned -- try uint instead of int, etc.");
124 
125     alias __hash_func = kh_hash!KT.kh_hash_func;
126     alias __hash_equal= kh_hash!KT.kh_hash_equal;
127 
128     alias kh_t = khash; /// klib uses 'kh_t' struct name
129 
130     struct khash        // @suppress(dscanner.style.phobos_naming_convention)
131     {
132         khint_t n_buckets, size, n_occupied, upper_bound;
133         khint32_t* flags;
134         KT* keys;
135         VT* vals;
136 
137         ~this()
138         {
139             //kh_destroy(&this); // the free(h) at the end of kh_destroy will SIGSEGV
140             static if (useGC) {
141                 GC.removeRange(this.keys);
142                 static if (kh_is_map)
143                     GC.removeRange(this.vals);
144             }
145             kfree(cast(void*) this.keys);
146             kfree(cast(void*) this.flags);
147             kfree(cast(void*) this.vals);
148         }
149         
150         /// Lookup by key
151         ref VT opIndex(KT key)
152         {
153             auto x = kh_get(&this, key);
154             return this.vals[x];
155         }
156 
157         /// Assign by key
158         void opIndexAssign(VT val, KT key)
159         {
160             int absent;
161             //auto x = khash!(uint, char).kh_put(&this, key, &absent);
162             auto x = kh_put(&this, key, &absent);
163             //khash!(uint, char).kh_value(kh, k) = 10;
164             this.vals[x] = val;
165         }
166 
167         /// remove key/value pair
168         void remove(KT key)
169         {
170             auto x = kh_get(&this, key);
171             kh_del(&this, x);
172         }
173 
174         /// Get or create if does not exist; mirror built-in hashmap
175         /// https://dlang.org/spec/hash-map.html#inserting_if_not_present
176         ref VT require(KT key, lazy VT initval)
177         {
178             static assert (kh_is_map == true, "require() not sensible in a hash set");
179             auto x = kh_get(&this, key);
180             if (x == kh_end(&this)) {
181                 // not present
182                 int absent;
183                 x = kh_put(&this, key, &absent);
184                 this.vals[x] = initval;
185             }
186             return this.vals[x];
187         }
188 
189         /// Return an InputRange over the keys.
190         /// Manipulating the hash table during iteration results in undefined behavior.
191         /// Returns: Voldemort type
192         auto byKey()
193         {
194             /** Manipulating the hash table during iteration results in undefined behavior */
195             struct KeyRange
196             {
197                 kh_t* kh;
198                 khint_t itr;
199                 bool empty()    // non-const as may call popFront
200                 {
201                     //return (this.itr == kh_end(this.kh));
202                     if (this.itr == kh_end(this.kh)) return true;
203                     // Handle the case of deleted keys
204                     else if (!kh_exists(this.kh, this.itr)) {
205                         while(!kh_exists(this.kh, this.itr)) {
206                             this.popFront();
207                             if (this.itr == kh_end(this.kh)) return true;
208                         }
209                         return false;
210                     }
211                     return false;
212                 }
213                 ref KT front()
214                 {
215                     return kh.keys[this.itr];
216                 }
217                 void popFront()
218                 {
219                     if(this.itr < kh_end(this.kh)) {
220                         this.itr++;
221                     }
222                 }
223             }
224             return KeyRange(&this);
225         }
226     }
227   
228     void kh_clear(kh_t* h);
229     int kh_resize(kh_t* h, khint_t new_n_buckets);
230     khint_t kh_put(kh_t* h, KT key, int* ret);
231     void kh_del(kh_t* h, khint_t x);
232   
233     deprecated("kept for source-level homology; use D-style RAII")
234     kh_t* kh_init()
235     {
236         return cast(kh_t*) kcalloc(1, kh_t.sizeof);
237     }
238   
239     deprecated("kept for source-level homology; kfree(h) will SIGSEGV when called as kh_destroy(&this)")
240     void kh_destroy(kh_t* h)
241     {
242         if (h)
243         {
244             kfree(cast(void*) h.keys);
245             kfree(cast(void*) h.flags);
246             kfree(cast(void*) h.vals);
247             kfree(h);
248         }
249     }
250   
251     void kh_clear(kh_t* h)
252     {
253       if (h && h.flags)
254       {
255         memset(h.flags, 0xaa, __ac_fsize(h.n_buckets) * khint32_t.sizeof);
256         h.size = h.n_occupied = 0;
257       }
258     }
259   
260     khint_t kh_get(const(kh_t)* h, KT key)
261     {
262         if (h.n_buckets)
263         {
264           khint_t k, i, last, mask, step = 0;
265           mask = h.n_buckets - 1;
266           k = __hash_func(key);
267           i = k & mask;
268           last = i;
269           while (!__ac_isempty(h.flags, i) && (__ac_isdel(h.flags, i) || !__hash_equal(h.keys[i], key)))
270           {
271               i = (i + (++step)) & mask;
272               if (i == last)
273                   return h.n_buckets;
274           }
275           return __ac_iseither(h.flags, i) ? h.n_buckets : i;
276         }
277         else
278             return 0;
279     }
280 
281     int kh_resize(kh_t *h, khint_t new_n_buckets)
282 	{
283         /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */
284 		khint32_t *new_flags = null;
285 		khint_t j = 1;
286 		{
287 			kroundup32(new_n_buckets);
288 			if (new_n_buckets < 4) new_n_buckets = 4;
289 			if (h.size >= cast(khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0;	/* requested size is too small */
290 			else { /* hash table size to be changed (shrink or expand); rehash */
291 				new_flags = cast(khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * khint32_t.sizeof);
292 				if (!new_flags) return -1;
293 				memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * khint32_t.sizeof);
294 				if (h.n_buckets < new_n_buckets) {	/* expand */
295 					KT *new_keys = cast(KT*)krealloc(cast(void *)h.keys, new_n_buckets * KT.sizeof);
296 					if (!new_keys) { kfree(new_flags); return -1; }
297                     static if (useGC) {
298                         GC.addRange(new_keys, new_n_buckets * KT.sizeof);
299                         if (new_keys != h.keys) GC.removeRange(h.keys);
300                     }
301 					h.keys = new_keys;
302 					static if (kh_is_map) {
303 						VT *new_vals = cast(VT*)krealloc(cast(void *)h.vals, new_n_buckets * VT.sizeof);
304 						if (!new_vals) { kfree(new_flags); return -1; }
305                         static if (useGC) {
306                             GC.addRange(new_vals, new_n_buckets * VT.sizeof);
307                             if (new_vals != h.vals) GC.removeRange(h.vals);
308                         }
309 						h.vals = new_vals;
310 					}
311 				} /* otherwise shrink */
312 			}
313 		}
314 		if (j) { /* rehashing is needed */
315 			for (j = 0; j != h.n_buckets; ++j) {
316 				if (__ac_iseither(h.flags, j) == 0) {
317 					KT key = h.keys[j];
318 					VT val;
319 					khint_t new_mask;
320 					new_mask = new_n_buckets - 1;
321 					static if (kh_is_map) val = h.vals[j];
322 					__ac_set_isdel_true(h.flags, j);
323 					while (1) { /* kick-out process; sort of like in Cuckoo hashing */
324 						khint_t k, i, step = 0;
325 						k = __hash_func(key);
326 						i = k & new_mask;
327 						while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask;
328 						__ac_set_isempty_false(new_flags, i);
329 						if (i < h.n_buckets && __ac_iseither(h.flags, i) == 0) { /* kick out the existing element */
330 							{ KT tmp = h.keys[i]; h.keys[i] = key; key = tmp; }
331 							static if (kh_is_map) { VT tmp = h.vals[i]; h.vals[i] = val; val = tmp; }
332 							__ac_set_isdel_true(h.flags, i); /* mark it as deleted in the old hash table */
333 						} else { /* write the element and jump out of the loop */
334 							h.keys[i] = key;
335 							static if (kh_is_map) h.vals[i] = val;
336 							break;
337 						}
338 					}
339 				}
340 			}
341 			if (h.n_buckets > new_n_buckets) { /* shrink the hash table */
342 				h.keys = cast(KT*)krealloc(cast(void *)h.keys, new_n_buckets * KT.sizeof);
343 				static if (kh_is_map) h.vals = cast(VT*)krealloc(cast(void *)h.vals, new_n_buckets * VT.sizeof);
344                 static if (useGC) {
345                     GC.disable();
346                     GC.removeRange(h.keys);
347                     GC.addRange(h.keys, new_n_buckets * KT.sizeof);
348                     static if (kh_is_map) {
349                         GC.removeRange(h.vals);
350                         GC.addRange(h.vals, new_n_buckets * VT.sizeof);
351                     }
352                     GC.enable();
353                 }
354 			}
355 			kfree(h.flags); /* free the working space */
356 			h.flags = new_flags;
357 			h.n_buckets = new_n_buckets;
358 			h.n_occupied = h.size;
359 			h.upper_bound = cast(khint_t)(h.n_buckets * __ac_HASH_UPPER + 0.5);
360 		}
361 		return 0;
362 	}
363 
364 	khint_t kh_put(kh_t *h, KT key, int *ret)
365 	{
366 		khint_t x;
367 		if (h.n_occupied >= h.upper_bound) { /* update the hash table */
368 			if (h.n_buckets > (h.size<<1)) {
369 				if (kh_resize(h, h.n_buckets - 1) < 0) { /* clear "deleted" elements */
370 					*ret = -1; return h.n_buckets;
371 				}
372 			} else if (kh_resize(h, h.n_buckets + 1) < 0) { /* expand the hash table */
373 				*ret = -1; return h.n_buckets;
374 			}
375 		} /* TODO: to implement automatically shrinking; resize() already support shrinking */
376 		{
377 			khint_t k, i, site, last, mask = h.n_buckets - 1, step = 0;
378 			x = site = h.n_buckets; k = __hash_func(key); i = k & mask;
379 			if (__ac_isempty(h.flags, i)) x = i; /* for speed up */
380 			else {
381 				last = i;
382 				while (!__ac_isempty(h.flags, i) && (__ac_isdel(h.flags, i) || !__hash_equal(h.keys[i], key))) {
383 					if (__ac_isdel(h.flags, i)) site = i;
384 					i = (i + (++step)) & mask;
385 					if (i == last) { x = site; break; }
386 				}
387 				if (x == h.n_buckets) {
388 					if (__ac_isempty(h.flags, i) && site != h.n_buckets) x = site;
389 					else x = i;
390 				}
391 			}
392 		}
393 		if (__ac_isempty(h.flags, x)) { /* not present at all */
394 			h.keys[x] = key;
395 			__ac_set_isboth_false(h.flags, x);
396 			++h.size; ++h.n_occupied;
397 			*ret = 1;
398 		} else if (__ac_isdel(h.flags, x)) { /* deleted */
399 			h.keys[x] = key;
400 			__ac_set_isboth_false(h.flags, x);
401 			++h.size;
402 			*ret = 2;
403 		} else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */
404 		return x;
405 	}
406 
407     void kh_del(kh_t *h, khint_t x)
408     {
409         if (x != h.n_buckets && !__ac_iseither(h.flags, x)) {
410             __ac_set_isdel_true(h.flags, x);
411             --h.size;
412         }
413     }
414 
415     auto kh_exists(const(kh_t)* h, khint_t x)
416     {
417         return (!__ac_iseither(h.flags, x));
418     }
419 
420     auto kh_key(const(kh_t)* h, khint_t x)
421     {
422         return h.keys[x];
423     }
424 
425     auto kh_val(const(kh_t)* h, khint_t x)
426     {
427         return h.vals[x];
428     }
429 
430     auto kh_begin(const(kh_t)* h)
431     {
432         return cast(khint_t) 0;
433     }
434 
435     auto kh_end(const(kh_t)* h)
436     {
437         return h.n_buckets;
438     }
439 
440     auto kh_size(const(kh_t)* h)
441     {
442         return h.size;
443     }
444 
445     alias kh_n_buckets = kh_end;
446 
447     alias kh_value = kh_val;
448 
449 }
450 
451 /** --- BEGIN OF HASH FUNCTIONS --- */
452 template kh_hash(T)
453 {
454 pragma(inline, true)
455 {
456     auto kh_hash_func(T)(T key)
457     if (is(T == uint) || is(T == uint32_t) || is(T == khint32_t))
458     {
459         return key;
460     }
461 
462     bool kh_hash_equal(T)(T a, T b)
463     if (isNumeric!T)
464     {
465         return (a == b);
466     }
467 
468     auto kh_hash_func(T)(T key)
469     if (is(T == ulong) || is(T == uint64_t) || is(T == khint64_t))
470     {
471         return cast(khint32_t) ((key)>>33^(key)^(key)<<11);
472     }
473 
474     auto kh_hash_func(T)(T* key)
475     if(is(T == char) || is(T == const(char)) || is(T == immutable(char)))
476     {
477         return __ac_X31_hash_string(key);
478     }
479 
480     bool kh_hash_equal(T)(scope const T* a, scope const T* b)
481     if(is(T == char) || is(T == const(char)) || is(T == immutable(char)))
482     {
483         return (strcmp(a, b) == 0);
484     }
485 
486     bool kh_hash_equal(T)(T a, T b)
487     if(isSomeString!T)
488     {
489         return (a == b);
490     }
491 
492     auto __ac_Wang_hash(T)(T key)
493     if (is(T == uint) || is(T == uint32_t) || is(T == khint32_t))
494     {
495         key += ~(key << 15);
496         key ^=  (key >> 10);
497         key +=  (key << 3);
498         key ^=  (key >> 6);
499         key += ~(key << 11);
500         key ^=  (key >> 16);
501         return key;
502     }
503 
504     // TODO
505     alias kh_int_hash_func2 = __ac_Wang_hash;
506 
507 } // end pragma(inline, true)
508 
509     khint_t __ac_X31_hash_string(const(char)* s)
510     {
511         version (DigitalMars) {} else pragma(inline, true);
512         khint_t h = cast(khint_t)*s;
513         if (h) for  (++s; *s; ++s) h = (h << 5) - h + cast(khint_t)*s;
514         return h;
515     }
516 
517     auto kh_hash_func(T)(T key)
518     if(isSomeString!T)
519     {
520         // rewrite __ac_X31_hash_string for D string/smart array
521         version (DigitalMars) {} else pragma(inline, true);
522         if (key.length == 0) return 0;
523         khint_t h = key[0];
524         for (int i=1; i<key.length; ++i)
525             h = (h << 5) - h + cast(khint_t) key[i];
526         return h;
527     }
528 
529 } // end template kh_hash
530 
531 /* --- END OF HASH FUNCTIONS --- */
532 
533 /* Other convenient macros... */
534 
535 /*!
536   @abstract Type of the hash table.
537   @param  name  Name of the hash table [symbol]
538  */
539 //#define khash_t(name) kh_##name##_t
540 // Moved into template khash(KT, VT)
541 
542 /*! @function
543   @abstract     Initiate a hash table.
544   @param  name  Name of the hash table [symbol]
545   @return       Pointer to the hash table [khash_t(name)*]
546  */
547 //#define kh_init(name) kh_init_##name()
548 // Moved into template khash(KT, VT)
549 
550 /*! @function
551   @abstract     Destroy a hash table.
552   @param  name  Name of the hash table [symbol]
553   @param  h     Pointer to the hash table [khash_t(name)*]
554  */
555 //#define kh_destroy(name, h) kh_destroy_##name(h)
556 // Moved into template khash(KT, VT)
557 
558 /*! @function
559   @abstract     Reset a hash table without deallocating memory.
560   @param  name  Name of the hash table [symbol]
561   @param  h     Pointer to the hash table [khash_t(name)*]
562  */
563 //#define kh_clear(name, h) kh_clear_##name(h)
564 // Moved into template khash(KT, VT)
565 
566 /*! @function
567   @abstract     Resize a hash table.
568   @param  name  Name of the hash table [symbol]
569   @param  h     Pointer to the hash table [khash_t(name)*]
570   @param  s     New size [khint_t]
571  */
572 //#define kh_resize(name, h, s) kh_resize_##name(h, s)
573 // Moved into template khash(KT, VT)
574 
575 /*! @function
576   @abstract     Insert a key to the hash table.
577   @param  name  Name of the hash table [symbol]
578   @param  h     Pointer to the hash table [khash_t(name)*]
579   @param  k     Key [type of keys]
580   @param  r     Extra return code: -1 if the operation failed;
581                 0 if the key is present in the hash table;
582                 1 if the bucket is empty (never used); 2 if the element in
583 				the bucket has been deleted [int*]
584   @return       Iterator to the inserted element [khint_t]
585  */
586 //#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
587 // Moved into template khash(KT, VT)
588 
589 /*! @function
590   @abstract     Retrieve a key from the hash table.
591   @param  name  Name of the hash table [symbol]
592   @param  h     Pointer to the hash table [khash_t(name)*]
593   @param  k     Key [type of keys]
594   @return       Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
595  */
596 //#define kh_get(name, h, k) kh_get_##name(h, k)
597 // Moved into template khash(KT, VT)
598 
599 /*! @function
600   @abstract     Remove a key from the hash table.
601   @param  name  Name of the hash table [symbol]
602   @param  h     Pointer to the hash table [khash_t(name)*]
603   @param  k     Iterator to the element to be deleted [khint_t]
604  */
605 //#define kh_del(name, h, k) kh_del_##name(h, k)
606 // Moved into template khash(KT, VT)
607 
608 /*! @function
609   @abstract     Test whether a bucket contains data.
610   @param  h     Pointer to the hash table [khash_t(name)*]
611   @param  x     Iterator to the bucket [khint_t]
612   @return       1 if containing data; 0 otherwise [int]
613  */
614 //#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
615 // Moved into template khash(KT, VT)
616 
617 /*! @function
618   @abstract     Get key given an iterator
619   @param  h     Pointer to the hash table [khash_t(name)*]
620   @param  x     Iterator to the bucket [khint_t]
621   @return       Key [type of keys]
622  */
623 //#define kh_key(h, x) ((h)->keys[x])
624 // Moved into template khash(KT, VT)
625 
626 /*! @function
627   @abstract     Get value given an iterator
628   @param  h     Pointer to the hash table [khash_t(name)*]
629   @param  x     Iterator to the bucket [khint_t]
630   @return       Value [type of values]
631   @discussion   For hash sets, calling this results in segfault.
632  */
633 //#define kh_val(h, x) ((h)->vals[x])
634 // Moved into template khash(KT, VT)
635 
636 /*! @function
637   @abstract     Alias of kh_val()
638  */
639 //#define kh_value(h, x) ((h)->vals[x])
640 // Moved into template khash(KT, VT)
641 
642 /*! @function
643   @abstract     Get the start iterator
644   @param  h     Pointer to the hash table [khash_t(name)*]
645   @return       The start iterator [khint_t]
646  */
647 //#define kh_begin(h) (khint_t)(0)
648 // Moved into template khash(KT, VT)
649 
650 /*! @function
651   @abstract     Get the end iterator
652   @param  h     Pointer to the hash table [khash_t(name)*]
653   @return       The end iterator [khint_t]
654  */
655 //#define kh_end(h) ((h)->n_buckets)
656 // Moved into template khash(KT, VT)
657 
658 /*! @function
659   @abstract     Get the number of elements in the hash table
660   @param  h     Pointer to the hash table [khash_t(name)*]
661   @return       Number of elements in the hash table [khint_t]
662  */
663 //#define kh_size(h) ((h)->size)
664 // Moved into template khash(KT, VT)
665 
666 /*! @function
667   @abstract     Get the number of buckets in the hash table
668   @param  h     Pointer to the hash table [khash_t(name)*]
669   @return       Number of buckets in the hash table [khint_t]
670  */
671 //#define kh_n_buckets(h) ((h)->n_buckets)
672 // Moved into template khash(KT, VT)
673 
674 /++ foreach: TODO
675 
676 /*! @function
677   @abstract     Iterate over the entries in the hash table
678   @param  h     Pointer to the hash table [khash_t(name)*]
679   @param  kvar  Variable to which key will be assigned
680   @param  vvar  Variable to which value will be assigned
681   @param  code  Block of code to execute
682  */
683 auto kh_foreach(kh_t* h, kvar, vvar, code)
684 {
685     khint_t __i;
686     for (__i = kh_begin(h); __i != kh_end(h); ++__i) {
687         if (!kh_exist(h, __i)) continue;
688         kvar = kh_key(h, __i);
689         vvar = kh_val(h, __i);
690         code;
691     }
692 }
693 
694 /*! @function
695   @abstract     Iterate over the values in the hash table
696   @param  h     Pointer to the hash table [khash_t(name)*]
697   @param  vvar  Variable to which value will be assigned
698   @param  code  Block of code to execute
699  */
700  auto kh_foreach_value(kh_t* h, vvar, code)
701  {
702      khint_t __i;
703      for (__i = kh_begin(h); __i != kh_end(h); ++__i) {
704          if (!kh_exist(h, __i)) continue;
705          vvar = kh_val(h, __i);
706          code;
707      }
708  }
709 +/
710 unittest
711 {
712     import std.stdio : writeln, writefln;
713 
714     writeln("khash unit tests");
715 
716     // test: numeric key type must be unsigned
717     assert(__traits(compiles, khash!(int, int)) is false);
718     assert(__traits(compiles, khash!(uint,int)) is true);
719 
720 //    auto kh = khash!(uint, char).kh_init();
721 
722     //int absent;
723     //auto k = khash!(uint, char).kh_put(kh, 5, &absent);
724     ////khash!(uint, char).kh_value(kh, k) = 10;
725     //kh.vals[k] = 'J';
726 
727 //    (*kh)[5] = 'J';
728 //    writeln("Entry value:", (*kh)[5]);
729     
730 //    khash!(uint, char).kh_destroy(kh);
731 
732     auto kh = khash!(uint, char)();
733     kh[5] = 'J';
734     assert(kh[5] == 'J');
735 
736     kh[1] = 'O';
737     kh[99] = 'N';
738 
739     // test: foreach by key
740     /*foreach(k; kh.byKey())
741         writefln("Key: %s", k);*/
742     import std.array : array;
743     assert(kh.byKey().array == [5, 1, 99]);
744 
745     // test: byKey on Empty hash table
746     auto kh_empty = khash!(uint, char)(); // @suppress(dscanner.suspicious.unmodified)
747     assert(kh_empty.byKey.array == []);
748 
749     // test: keytype string
750     auto kh_string = khash!(string, int)();
751     kh_string["test"] = 5;
752     assert( kh_string["test"] == 5 );
753 
754     // test: valtype string
755     auto kh_valstring = khash!(uint, string)();
756     kh_valstring[42] = "Adams";
757     assert( kh_valstring[42] == "Adams" );
758 
759     // test: require
760     const auto fw = kh_string.require("flammenwerfer", 21);
761     assert(fw == 21);
762 
763     // test: kh_hash_equal template type qualifiers and constraints
764     {
765         // test: can instantiate with char* key
766         // (Fails without 'const' qualifier on kh_hash_equal
767         { khash!(char*, int) _; }
768 
769         string s = "Hello";
770         string t = "Hel" ~ "lo";
771         assert( kh_hash!(immutable char).kh_hash_equal(s.ptr, t.ptr) );
772         assert( kh_hash!(string).kh_hash_equal(s, t) );
773     }
774 }