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 }