1 module nxt.open_hashmap; 2 3 // version = showEntries; 4 5 import core.internal.hash : hashOf; 6 import nxt.nullable_traits : isNullable; 7 import nxt.pure_mallocator : Mallocator = PureMallocator; // TODO merge into `std.experimental.allocator.mallocator` 8 9 enum Flag 10 { 11 borrowChecked, 12 useSmallLinearSearch, 13 usePrimeCapacity, 14 } 15 import std.typecons : BitFlags; 16 alias Flags = BitFlags!Flag; ///< Use as Flags flags param to `OpenHashMap` 17 18 @safe: 19 20 /** Hash table/map with in-place open-addressing, storing `keys` of type `K` and 21 * values of type `V`. 22 * 23 * Keys are immutable except for when they are `class`es in which case they are 24 * head-const (through bin reinterpretation to `KeyValueType`), This can be 25 * overridden by setting `keyEqualPred` to, for instance, `a == b` for `class` 26 * keys. 27 * 28 * Uses quadratic probing (using triangular numbers) unless `usePrimeCapacity` 29 * in which case a simpler probing is used. 30 * 31 * Deletion/Removal of elements is lazy via the bitmap `_holesPtr` or through 32 * assignment of of reserved value of `KeyType` when `KeyType` has hole-support 33 * via trait `isHoleable`. 34 * 35 * Element iteration via 36 * - either `byKey`, `byValue` or `byKeyValue` over `OpenHashMap` and 37 * - `byElement` over `OpenHashset` 38 * respects taking the container argument either as an l-value or r-value using 39 * detected using `auto ref`-qualified parameter introspected using `(__traits(isRef, y))`. 40 * In the r-value case no reference counting is needed. 41 * In the l-value case setting `borrowChecked` to `true` adds run-time 42 * support for dynamic Rust-style ownership and borrowing between the range and the container. 43 * 44 * Params: 45 * K = key type 46 * V = value type 47 * hasher = hash function or std.digest Hash 48 * Allocator = memory allocator for bin array 49 * borrowChecked = only activate when it's certain that this won't be moved via std.algorithm.mutation.move() 50 * useSmallLinearSearch = Use linear search instead probing when `_store` is smaller than `linearSearchMaxSize` 51 * usePrimeCapacity = Use prime numbers as capacity of hash table enabling better performance of simpler hash-functions 52 * 53 * See_Also: https://www.sebastiansylvan.com/post/robin-hood-hashing-should-be-your-default-hash-table-implementation/ 54 * See_Also: https://arxiv.org/abs/1605.04031 55 * See_Also: https://github.com/Tessil/robin-map 56 * See_Also: https://github.com/martinus/robin-hood-hashing 57 * See_Also: https://probablydance.com/2017/02/26/i-wrote-the-fastest-hashtable/ 58 * See_Also: https://en.wikipedia.org/wiki/Lazy_deletion 59 * See_Also: https://forum.dlang.org/post/ejqhcsvdyyqtntkgzgae@forum.dlang.org 60 * See_Also: https://gankro.github.io/blah/hashbrown-insert/ 61 * 62 * TODO tests fails when `useSmallLinearSearch` is set to `false` 63 * 64 * TODO Use set of `Flag`s (defined here) as template params 65 * 66 * TODO group nxt.probing functions in Prober struct given as type template 67 * param to `OpenHashMap` 68 * 69 * TODO Make load factor dependent on current capacity or length and perhaps 70 * also type and hash-function to get memory efficiency when it matters. Similar 71 * to what is recommended in https://ticki.github.io/blog/horrible/. 72 * 73 * TODO remove use of `static if (__traits(isCopyable, ...))` in cases where compiler can handle more moves 74 * 75 * TODO use mmap allocator when `_store.sizeof` is larger than at least 8 pages 76 * 77 * TODO use `StoreK` in store and cast between it and `KeyType` 78 * 79 * TODO allocate _holesPtr array together with _store to reduce size of 80 * `OpenHashMap` to 3 words when element type doesn't support it 81 * 82 * TODO fix bug in `growInPlaceWithCapacity` and benchmark 83 * 84 * TODO modify existing unittest for `struct Rel { const string name; }` 85 * 86 * TODO use allocator.dispose() instead of allocator.deallocate() as in 87 * https://github.com/dlang-community/containers 88 * 89 * TODO if hash-function is cast(size_t)(classInstance) always use prime length 90 * and shift pointer before hash based on alignof (might not be needed when 91 * module prime) to maximize memory locality when adding successively allocated 92 * pointers 93 * 94 * TODO add extractElement that moves it out similar to 95 * http://en.cppreference.com/w/cpp/container/unordered_set/extract 96 * 97 * TODO add merge or union algorithm here or into container_algorithm.d. See 98 * also: http://en.cppreference.com/w/cpp/container/unordered_set/merge. this 99 * algorithm moves elements from source if they are not already in `this` 100 * 101 * TODO Robin-Hood-hashing 102 * 103 * TODO enable `borrowChecked` unconditionally in version(debug) if and when 104 * `opMove` is implemented, in which case opMove() should assert false if this 105 * is borrowed. See: https://github.com/dlang/DIPs/pull/109 106 * 107 * TODO keep only predicates with ref arguments (`const scope auto ref`) when 108 * LDC can optimize those as fast as value passing. add LDC issue for this 109 * 110 * TODO save one word by making `_store.length` be inferred by 111 * `primeConstants[_primeIndex]` if this is not too costly 112 * 113 * TODO only add one extra element to capacity when `assumeNonFullHaystack` is `true` 114 */ 115 struct OpenHashMap(K, V = void, 116 alias hasher = hashOf, 117 string keyEqualPred = defaultKeyEqualPredOf!(K), 118 alias Allocator = Mallocator.instance, 119 bool borrowChecked = false, 120 bool useSmallLinearSearch = true, 121 bool usePrimeCapacity = false) 122 if (isNullable!K /*&& isHashable!K */) 123 { 124 // pragma(msg, K.stringof, " => ", V.stringof); 125 import core.exception : onOutOfMemoryError; 126 import core.internal.traits : hasElaborateDestructor, Unqual; 127 import core.lifetime : move; 128 import std.traits : hasIndirections, hasFunctionAttributes; 129 import std.typecons : Nullable; 130 131 import nxt.nullable_traits : defaultNullKeyConstantOf, isNull, nullify; 132 import nxt.container_traits : mustAddGCRange; 133 import nxt.qcmeman : gc_addRange, gc_removeRange; 134 135 static if (usePrimeCapacity) 136 { 137 import nxt.prime_modulo : PrimeIndex, ceilingPrime, moduloPrimeIndex; 138 } 139 else 140 { 141 import std.math : nextPow2; 142 import nxt.probing : triangularProbeFromIndex, triangularProbeFromIndexIncludingHoles, triangularProbeCountFromIndex; 143 /// Setting this `true` doesn't give measurable speedups so set it to `false` for now. 144 enum bool assumeNonFullHaystack = false; 145 } 146 147 static if (is(typeof(keyEqualPred) : string)) 148 { 149 import std.functional : binaryFun; 150 alias keyEqualPredFn = binaryFun!keyEqualPred; 151 } 152 else 153 { 154 alias keyEqualPredFn = keyEqualPred; 155 } 156 157 private enum isSlice(T) = is(T : const(E)[], E); 158 159 static if ((is(K == class)) && 160 keyEqualPred == `a is b`) // TODO use better predicate compare? 161 { 162 alias StoreK = void*; 163 } 164 else 165 { 166 import std.traits : isPointer; 167 static if (isPointer!K && 168 // TODO use better predicate compare? 169 (keyEqualPred == `a == b` || 170 keyEqualPred == `a is b`)) 171 { 172 alias StoreK = void*; 173 } 174 else 175 { 176 alias StoreK = K; 177 } 178 } 179 180 enum isBorrowChecked = borrowChecked; 181 182 /** In the hash map case, `V` is non-void, and a value is stored alongside 183 * the key of type `K`. 184 */ 185 enum hasValue = !is(V == void); 186 187 /** Is `true` iff `K` is an address, in which case holes are represented by 188 * a specific value `holeKeyConstant`. 189 */ 190 enum hasAddressLikeKey = (isAddress!K || 191 isSlice!K); 192 193 /** Stores less than or equal to this size will be searched using linear search. */ 194 private enum linearSearchMaxSize = 64; // one cache-line for now 195 196 static if (hasAddressLikeKey) 197 { 198 enum hasHoleableKey = true; 199 enum holeKeyOffset = 0x1; // TODO is this a good value? Or is 0xffff_ffff_ffff_ffff better? 200 @trusted enum holeKeyAddress = cast(void*)holeKeyOffset; 201 202 /** 203 * See_Also: https://forum.dlang.org/post/p7726n$2apd$1@digitalmars.com 204 * TODO test if ulong.max gives better performance 205 */ 206 static K holeKeyConstant() @trusted pure nothrow @nogc 207 { 208 pragma(inline, true); 209 // TODO note that cast(size_t*) will give address 0x8 instead of 0x1 210 static if (isSlice!K) 211 { 212 alias E = typeof(K.init[0])*; // array element type 213 auto ptr = cast(E)((cast(void*)null) + holeKeyOffset); // indicates a lazily deleted key 214 return ptr[0 .. 0]; 215 } 216 else 217 { 218 return cast(K)((cast(void*)null) + holeKeyOffset); // indicates a lazily deleted key 219 } 220 } 221 222 static bool isHoleKeyConstant(const scope K key) @trusted pure nothrow @nogc 223 { 224 pragma(inline, true); 225 static if (isSlice!K) // for slices 226 { 227 // suffice to compare pointer part 228 return (key.ptr is holeKeyAddress); 229 } 230 else 231 { 232 return (cast(const(void)*)key is holeKeyAddress); 233 } 234 } 235 236 /** TODO make these work 237 */ 238 // enum K holeKey_1 = cast(K)((cast(size_t*)null)); 239 // static immutable K holeKey_2 = cast(K)((cast(size_t*)null)); 240 } 241 else static if (isHoleable!K) 242 { 243 enum hasHoleableKey = true; 244 static K holeKeyConstant() @safe pure nothrow @nogc 245 { 246 pragma(inline, true); 247 return K.holeValue; 248 } 249 static bool isHoleKeyConstant(const scope K key) @safe pure nothrow @nogc 250 { 251 pragma(inline, true); 252 static if (__traits(hasMember, K, "isHole")) 253 { 254 // typically faster by asserting value of member of aggregate `K` 255 return key.isHole; 256 } 257 else 258 { 259 return key is K.holeValue; 260 } 261 } 262 } 263 else static if (__traits(hasMember, K, "nullifier")) 264 { 265 alias Nullifier = typeof(K.init.nullifier); 266 // TODO pragma(msg, K, " has nullifier ", Nullifier); 267 static if (isHoleable!Nullifier) 268 { 269 // TODO pragma(msg, K, " has holeable nullifier ", Nullifier); 270 enum hasHoleableKey = true; 271 static K holeKeyConstant() @trusted pure nothrow @nogc 272 { 273 K k; 274 k.nullifier = Nullifier.holeValue; 275 return k; 276 } 277 static bool isHoleKeyConstant(const scope K key) @trusted pure nothrow @nogc 278 { 279 return key.nullfier == Nullifier.holeValue; 280 } 281 } 282 else 283 { 284 enum hasHoleableKey = false; 285 // pragma(msg, "Need explicit hole bitset for non-address-like key: ", K); 286 import core.bitop : bts, bt, btr; 287 import nxt.array_help : makeUninitializedBitArray, makeZeroedBitArray, makeReallocatedBitArrayZeroPadded; 288 } 289 } 290 else 291 { 292 enum hasHoleableKey = false; 293 // pragma(msg, "Need explicit hole bitset for non-address-like key: ", K); 294 import core.bitop : bts, bt, btr; 295 import nxt.array_help : makeUninitializedBitArray, makeZeroedBitArray, makeReallocatedBitArrayZeroPadded; 296 } 297 298 /// Element type. 299 static if (hasValue) 300 { 301 /** Map insertion status. 302 */ 303 enum InsertionStatus 304 { 305 added, // element was added 306 modified, // value of element was changed (map only). TODO only for Map-case 307 unmodified // element was left unchanged 308 } 309 310 /// Mutable element reference with mutable constant key and value. 311 struct T 312 { 313 K key; 314 V value; 315 } 316 317 /// Get key part of element. 318 static auto ref inout(K) keyOf(SomeElement)(auto ref return scope inout(SomeElement) element) 319 { 320 pragma(inline, true); 321 return element.key; 322 } 323 324 /// Get value part of element. 325 static auto ref inout(V) valueOf()(auto ref return scope inout(T) element) 326 { 327 pragma(inline, true); 328 return element.value; 329 } 330 331 /** Type of key stored. */ 332 public alias KeyType = K; 333 334 /** Type of value stored. */ 335 public alias ValueType = V; 336 337 enum nullKeyElement = T(defaultNullKeyConstantOf!K, V.init); 338 339 /// Key-value element reference with head-const for `class` keys and mutable value. 340 static private struct KeyValueType 341 { 342 static if (isAddress!K) // for reference types 343 { 344 K _key; // no const because 345 346 /** Key access is head-const. */ 347 inout(K) key() @property inout @safe pure nothrow @nogc 348 { 349 return _key; 350 } 351 } 352 else 353 { 354 const K key; 355 } 356 V value; 357 } 358 359 /// Get key part. 360 static auto ref inout(K) keyOf()(auto ref return scope inout(KeyValueType) element) @trusted 361 { 362 pragma(inline, true); 363 return cast(typeof(return))element.key; // needed for case: `inout(const(K)) => inout(K)` 364 } 365 } 366 else // HashSet 367 { 368 /** Set insertion status. 369 */ 370 enum InsertionStatus 371 { 372 added, // element was added 373 unmodified // element was left unchanged 374 } 375 376 alias T = K; // short name for element type 377 378 /// Get key part of element. 379 static auto ref inout(SomeElement) keyOf(SomeElement)(auto ref return inout(SomeElement) element) 380 { 381 pragma(inline, true); 382 return element; 383 } 384 385 enum nullKeyElement = defaultNullKeyConstantOf!K; 386 } 387 388 /** Is `true` if an instance of `SomeKey` that can be implictly cast to `K`. 389 * 390 * For instance `const(char)[]` can be `@trusted`ly cast to `string` in a 391 * temporary scope. 392 */ 393 template isScopedKeyType(SomeKey) 394 { 395 static if (is(SomeKey == class)) 396 { 397 enum isScopedKeyType = (is(const(SomeKey) : const(K))); 398 } 399 else 400 { 401 enum isScopedKeyType = (is(K : SomeKey) || // `K is` implicitly convertible from `SomeKey` 402 is(SomeKey : U[], U) && // is array 403 is(typeof(K(SomeKey.init)))); 404 } 405 } 406 407 alias ElementType = T; 408 409 /** Make with room for storing at least `minimumCapacity` number of elements. 410 * 411 * See_Also: 412 * https://forum.dlang.org/post/nyngzsaeqxzzuumivtze@forum.dlang.org 413 */ 414 static typeof(this) withCapacity()(size_t minimumCapacity) // template-lazy 415 { 416 version(showEntries) dbg(__FUNCTION__, " minimumCapacity:", minimumCapacity); 417 static if (usePrimeCapacity) 418 { 419 PrimeIndex primeIndex; 420 immutable initialCapacity = ceilingPrime(minimumCapacity + 1, primeIndex); 421 assert(minimumCapacity < initialCapacity); // we need at least one vacancy 422 return typeof(return)(makeDefaultInitializedStoreOfCapacity(initialCapacity), primeIndex, 0); 423 } 424 else 425 { 426 immutable initialCapacity = nextPow2(minimumCapacity); 427 assert(minimumCapacity < initialCapacity); // we need at least one vacancy 428 return typeof(return)(makeDefaultInitializedStoreOfCapacity(initialCapacity), 0); 429 } 430 } 431 432 /** Make default-initialized store with `capacity` number of slots. 433 */ 434 static private T[] makeDefaultInitializedStoreOfCapacity()(in size_t capacity) @trusted pure nothrow @nogc // template-lazy 435 { 436 static if (usePrimeCapacity) 437 { 438 // TODO check that capacity is prime? 439 } 440 else 441 { 442 debug import std.math : isPowerOf2; 443 debug assert(isPowerOf2(capacity)); // quadratic probing needs power of two capacity (`_store.length`) 444 } 445 version(showEntries) dbg(__FUNCTION__, " minimumCapacity:", 446 minimumCapacity, 447 " capacity:", capacity); 448 449 // TODO cannot use makeArray here because it cannot handle uncopyable types 450 // import std.experimental.allocator : makeArray; 451 // auto store = Allocator.makeArray!T(capacity, nullKeyElement); 452 453 import nxt.bit_traits : isAllZeroBits; 454 455 immutable byteCount = T.sizeof*capacity; 456 457 static if (hasAddressLikeKey || 458 (__traits(isZeroInit, K) && 459 __traits(hasMember, K, "nullifier")) || 460 // TODO add check for __traits(isZeroInit, K) and member `K.nullValue` == `K.init` 461 (__traits(hasMember, K, `nullValue`) && // if key has a null value 462 __traits(compiles, { enum _ = isAllZeroBits!(K, K.nullValue); }) && // prevent strange error given when `K` is `knet.data.Data` 463 isAllZeroBits!(K, K.nullValue))) // check that it's zero bits only 464 { 465 // pragma(msg, "zero-allocate:", "K:", K, " V:", V); 466 // TODO use std.experimental.allocator.makeArray instead of this which handles clever checking for isZeroInit 467 import nxt.container_traits : makeInitZeroArray; 468 auto store = makeInitZeroArray!(T, Allocator)(capacity); 469 if (store.ptr is null && capacity >= 1) 470 { 471 onOutOfMemoryError(); 472 } 473 } 474 else // when default null key is not represented by zeros 475 { 476 // pragma(msg, "emplace:", "K:", K, " V:", V); 477 auto store = cast(T[])Allocator.allocate(byteCount); 478 if (store.ptr is null && byteCount >= 1) 479 { 480 onOutOfMemoryError(); 481 } 482 foreach (ref bin; store) 483 { 484 import core.lifetime : emplace; 485 enum hasNullValueKey = __traits(hasMember, K, `nullValue`); 486 static if (hasNullValueKey && 487 !is(typeof(emplace(&keyOf(bin), K.nullValue)))) // __traits(compiles) fails here when building knet 488 { 489 pragma(msg, __FILE__, ":", __LINE__, ":warning: emplace fails for null-Value key type ", K); 490 } 491 492 // initialize key 493 static if (hasNullValueKey && 494 is(typeof(emplace(&keyOf(bin), K.nullValue)))) // __traits(compiles) fails here when building knet 495 { 496 emplace(&keyOf(bin), K.nullValue); 497 } 498 else 499 { 500 emplace(&keyOf(bin)); 501 keyOf(bin).nullify(); // moveEmplace doesn't init source of type Nullable 502 } 503 504 // initialize value 505 static if (hasValue) 506 { 507 static if (is(V == class)) 508 { 509 valueOf(bin) = null; // just copy class value 510 } 511 else 512 { 513 // TODO only needed when `hasElaborateDestructor` 514 emplace(&valueOf(bin)); // construct in-place 515 } 516 } 517 } 518 } 519 520 static if (mustAddGCRange!T) 521 { 522 gc_addRange(store.ptr, byteCount); 523 } 524 525 return store; 526 } 527 528 static private T[] allocateUninitializedStore()(size_t capacity) @trusted pure nothrow @nogc // template-lazy 529 { 530 version(LDC) pragma(inline, true); 531 version(showEntries) dbg(__FUNCTION__, " newCapacity:", capacity); 532 immutable byteCount = T.sizeof*capacity; 533 auto store = cast(typeof(return))Allocator.allocate(byteCount); 534 static if (mustAddGCRange!T) 535 { 536 gc_addRange(store.ptr, byteCount); 537 } 538 if (store.ptr is null && byteCount >= 1) 539 { 540 onOutOfMemoryError(); 541 } 542 return store; 543 } 544 545 import std.range.primitives : StdElementType = ElementType; 546 import std.traits : isIterable, isAssignable; 547 548 /** Make with the element `element`. */ 549 this(T element) 550 { 551 static if (usePrimeCapacity) 552 { 553 _primeIndex = PrimeIndex.init; 554 _store = makeDefaultInitializedStoreOfCapacity(ceilingPrime(1 + 1, _primeIndex)); 555 } 556 else 557 { 558 _store = makeDefaultInitializedStoreOfCapacity(nextPow2(1)); 559 } 560 _count = 0; 561 static if (__traits(isCopyable, T)) 562 { 563 insertWithoutGrowthNoStatus(element); 564 } 565 else 566 { 567 insertWithoutGrowthNoStatus(move(element)); 568 } 569 } 570 571 static if (hasHoleableKey) 572 { 573 private this(T[] store, size_t count) 574 { 575 _store = store; 576 _count = count; 577 } 578 } 579 else 580 { 581 private this(T[] store, size_t count, size_t* holesPtr = null) 582 { 583 _store = store; 584 _count = count; 585 _holesPtr = holesPtr; 586 } 587 } 588 589 /** Make with the elements `elements`. */ 590 static typeof(this) withElements(R)(R elements) 591 if (isIterable!R && 592 isAssignable!(T, StdElementType!R)) 593 { 594 version(showEntries) dbg(__FUNCTION__, " length:", elements.length); 595 import std.range.primitives : hasLength; 596 static if (hasLength!R) 597 { 598 typeof(this) that = withCapacity(elements.length); 599 foreach (element; elements) 600 { 601 that.insertWithoutGrowthNoStatus(element); 602 } 603 } 604 else 605 { 606 typeof(this) that; 607 foreach (ref element; elements) 608 { 609 that.insert(element); 610 } 611 } 612 return that; 613 } 614 615 /// Destruct. 616 ~this() @nogc 617 { 618 version(LDC) pragma(inline, true); 619 release(); 620 } 621 622 /// No copying. 623 @disable this(this); 624 625 /// Returns: a shallow duplicate of `this`. 626 typeof(this) dup()() const @trusted // template-lazy 627 { 628 version(showEntries) dbg(__FUNCTION__, " length:", length); 629 T[] storeCopy = allocateUninitializedStore(_store.length); // unsafe 630 foreach (immutable index, ref bin; _store) 631 { 632 if (isOccupiedAtIndex(index)) // normal case 633 { 634 static if (hasValue) // map 635 { 636 duplicateEmplace(bin.key, storeCopy[index].key); 637 duplicateEmplace(bin.value, storeCopy[index].value); 638 } 639 else // set 640 { 641 duplicateEmplace(bin, storeCopy[index]); 642 } 643 } 644 else 645 { 646 import core.lifetime : emplace; 647 emplace(&storeCopy[index]); // TODO only emplace key and not value 648 keyOf(storeCopy[index]).nullify(); 649 } 650 } 651 static if (!hasHoleableKey) 652 { 653 if (_holesPtr) 654 { 655 immutable wordCount = holesWordCount(_store.length); 656 657 auto holesPtrCopy = makeUninitializedBitArray!Allocator(_store.length); 658 holesPtrCopy[0 .. wordCount] = _holesPtr[0 .. wordCount]; // TODO use memcpy instead? 659 660 return typeof(return)(storeCopy, _count, holesPtrCopy); 661 } 662 } 663 return typeof(return)(storeCopy, _count); 664 } 665 666 /// Equality. 667 bool opEquals()(const scope auto ref typeof(this) rhs) const 668 { 669 if (_count != rhs._count) { return false; } // quick discardal 670 foreach (immutable index, const ref bin; _store) 671 { 672 if (isOccupiedAtIndex(index)) 673 { 674 static if (hasValue) 675 { 676 auto valuePtr = bin.key in rhs; 677 if (!valuePtr) 678 { 679 return false; 680 } 681 // TODO make != a parameter that can also be typically !is. TODO ask forum about this 682 if ((*valuePtr) != bin.value) 683 { 684 return false; 685 } 686 } 687 else 688 { 689 if (!rhs.contains(bin)) 690 { 691 return false; 692 } 693 } 694 } 695 } 696 return true; 697 } 698 699 static if (true) 700 { 701 pragma(inline, true): 702 private: 703 704 static if (!hasHoleableKey) 705 { 706 void deallocateHoles() @trusted 707 { 708 if (_holesPtr) 709 { 710 static if (__traits(hasMember, Allocator, "deallocatePtr")) 711 { 712 Allocator.deallocatePtr(_holesPtr); 713 } 714 else 715 { 716 Allocator.deallocate(_holesPtr[0 .. holesWordCount(_store.length)]); 717 } 718 } 719 } 720 721 static size_t* reallocateHoles(size_t[] holes, size_t byteCount) @trusted 722 { 723 auto rawHoles = cast(void[])holes; 724 const ok = Allocator.reallocate(rawHoles, byteCount); 725 assert(ok, "couldn't reallocate holes"); 726 return cast(typeof(return))rawHoles.ptr; 727 } 728 729 void clearHoles() 730 { 731 deallocateHoles(); 732 _holesPtr = null; 733 } 734 735 enum wordBytes = size_t.sizeof; 736 enum wordBits = 8*wordBytes; 737 738 /** Returns: number of words (`size_t`) needed to represent 739 * `binCount` holes. 740 */ 741 static size_t holesWordCount(size_t binCount) 742 { 743 return (binCount / wordBits + 744 (binCount % wordBits ? 1 : 0)); 745 } 746 747 static size_t binBlockBytes(size_t binCount) 748 { 749 return wordBytes*holesWordCount(binCount); 750 } 751 752 void untagHoleAtIndex(size_t index) @trusted 753 { 754 version(unittest) assert(index < _store.length); 755 if (_holesPtr !is null) 756 { 757 btr(_holesPtr, index); 758 } 759 } 760 761 static bool hasHoleAtPtrIndex(const scope size_t* holesPtr, size_t index) @trusted 762 { 763 return (holesPtr && 764 bt(holesPtr, index) != 0); 765 } 766 } 767 768 void tagHoleAtIndex(size_t index) @trusted 769 { 770 version(unittest) assert(index < _store.length); 771 static if (hasHoleableKey) 772 { 773 keyOf(_store[index]) = holeKeyConstant; 774 } 775 else 776 { 777 if (_holesPtr is null) // lazy allocation 778 { 779 _holesPtr = makeZeroedBitArray!Allocator(_store.length); 780 } 781 bts(_holesPtr, index); 782 } 783 } 784 785 } 786 787 static if (borrowChecked) 788 { 789 static immutable borrowedErrorMessage = "cannot mutate this when it's borrowed"; 790 } 791 792 /// Empty. 793 void clear()() // template-lazy 794 { 795 static if (borrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 796 release(); 797 _store = typeof(_store).init; 798 static if (usePrimeCapacity) 799 { 800 _primeIndex = 0; 801 } 802 static if (!hasHoleableKey) 803 { 804 _holesPtr = null; 805 } 806 _count = 0; 807 } 808 809 /// Release internal allocations. 810 private void release() scope 811 { 812 releaseBinElements(); 813 releaseStoreAndHolesSlices(); 814 } 815 816 /// Release bin elements. 817 private void releaseBinElements() scope 818 { 819 foreach (ref bin; _store) 820 { 821 static if (hasElaborateDestructor!T) 822 { 823 .destroy(bin); 824 } 825 } 826 } 827 828 /// Release bin slice. 829 private void releaseStoreAndHolesSlices() scope 830 { 831 releaseStoreSlice(_store); 832 static if (!hasHoleableKey) 833 { 834 deallocateHoles(); 835 } 836 } 837 838 static private void releaseStoreSlice(T[] store) @trusted 839 { 840 version(showEntries) dbg(__FUNCTION__, " store.ptr:", store.ptr, " store.length", store.length); 841 if (store.ptr is null) { return; } // `gc_removeRange` fails for null input 842 static if (mustAddGCRange!T) 843 { 844 gc_removeRange(store.ptr); // `gc_removeRange` fails for null input 845 } 846 static if (__traits(hasMember, Allocator, "deallocatePtr")) 847 { 848 Allocator.deallocatePtr(store.ptr); 849 } 850 else 851 { 852 Allocator.deallocate(store); 853 } 854 } 855 856 private auto adjustKeyType(SomeKey)(const return scope SomeKey key) const scope @trusted 857 { 858 pragma(inline, true); // must be inlined 859 static if (is(SomeKey : U[], U)) // is array (slice) 860 { 861 /* because return value is used only temporarily it's ok to cast to 862 * `immutable` to prevent GC-allocations in types such as 863 * `sso_string.SSOString` */ 864 return cast(immutable(typeof(key[0]))[])key; 865 } 866 else 867 { 868 return key; 869 } 870 } 871 872 /** Check if `element` is stored. 873 * 874 * Parameter `key` may be non-immutable, for instance const(char)[] 875 * eventhough key type `K` is `string`. 876 * 877 * Returns: `true` if element is present, `false` otherwise. 878 */ 879 bool contains(SomeKey)(const scope SomeKey key) const scope @trusted // template-lazy, `auto ref` here makes things slow 880 if (isScopedKeyType!(typeof(key))) 881 { 882 // pragma(msg, SomeKey.stringof ~ " " ~ K.stringof, " ", is(K : SomeKey), " ", is(SomeKey : K)); 883 // debug static assert(isScopedKeyType!(typeof(key)), SomeKey.stringof ~ " " ~ K.stringof); 884 version(LDC) pragma(inline, true); 885 886 assert(!key.isNull); 887 static if (hasHoleableKey) { assert(!isHoleKeyConstant(cast(const(K))adjustKeyType(key))); } 888 889 static if (useSmallLinearSearch) 890 { 891 if (_store.length * T.sizeof <= linearSearchMaxSize) 892 { 893 // dbg("using linear serach"); 894 return containsUsingLinearSearch(key); 895 } 896 else 897 { 898 // dbg("using normal serach"); 899 } 900 } 901 902 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(cast(const(K))adjustKeyType(key)); // cast scoped `key` is @trusted 903 904 version(none) 905 static if (SomeKey.stringof == "SSOString" || 906 is(SomeKey == const(char)[])) 907 { 908 dbg(SomeKey.stringof, " key:", key, 909 " store length:", _store.length, 910 " hitIndex:", hitIndex, 911 " isOcc:", isOccupiedAtIndex(hitIndex), 912 " store:", _store); 913 } 914 915 return (hitIndex != _store.length && 916 isOccupiedAtIndex(hitIndex)); 917 } 918 919 /** Check if `element` is stored. 920 * 921 * Uses linear search instead of hashing plus probing and may be faster for 922 * for small tables with complicated hash functions. 923 * 924 * Parameter `key` may be non-immutable, for instance const(char)[] 925 * eventhough key type `K` is `string`. 926 * 927 * Returns: `true` if element is present, `false` otherwise. 928 */ 929 bool containsUsingLinearSearch(SomeKey)(const scope SomeKey key) const scope @trusted // template-lazy, `auto ref` here makes things slow 930 if (isScopedKeyType!(typeof(key))) 931 { 932 assert(!key.isNull); 933 static if (hasHoleableKey) { assert(!isHoleKeyConstant(cast(const(K))adjustKeyType(key))); } 934 935 static if (isInstanceOf!(Nullable, SomeKey)) 936 { 937 import std.algorithm.searching : canFind; 938 import std.traits : TemplateArgsOf; 939 alias args = TemplateArgsOf!(SomeKey); 940 debug static assert(args.length == 2, 941 "linear search for Nullable without nullValue is slower than default `this.contains()` and is not allowed"); 942 alias UnderlyingType = args[0]; 943 return length >= 1 && (cast(UnderlyingType[])_store).canFind!keyEqualPredFn(key.get()); 944 } 945 else 946 { 947 foreach (const ref bin; _store) 948 { 949 if (keyEqualPredFn(keyOf(bin), key)) { return true; } 950 } 951 return false; 952 } 953 } 954 955 /** Check if `element` is stored. Move found element to a hole if possible. 956 Returns: `true` if element is present, `false` otherwise. 957 */ 958 bool containsWithHoleMoving()(const scope K key) // template-lazy, `auto ref` here makes things slow 959 { 960 version(LDC) pragma(inline, true); 961 962 assert(!key.isNull); 963 static if (hasHoleableKey) { assert(!isHoleKeyConstant(key)); } 964 static if (borrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 965 966 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKeyType(key)); 967 // TODO update holes 968 return (hitIndex != _store.length && 969 isOccupiedAtIndex(hitIndex)); 970 } 971 972 /** Insert `element`, being either a key-value (map-case) or a just a key 973 * (set-case). 974 * 975 * If `element` is a nullable type and it is null an `AssertError` is thrown. 976 */ 977 InsertionStatus insert()(const T element) @trusted // template-lazy. need `T` to be `const` in `class` case 978 { 979 version(LDC) pragma(inline, true); 980 981 assert(!keyOf(element).isNull); 982 static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(keyOf(element))); } 983 static if (borrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 984 985 reserveExtra(1); 986 size_t hitIndex = 0; 987 static if (__traits(isCopyable, T)) 988 { 989 return insertWithoutGrowth(element, hitIndex); 990 } 991 else 992 { 993 return insertWithoutGrowth(move(*cast(T*)&element), hitIndex); 994 } 995 } 996 997 /** Insert `element`, being either a key-value (map-case) or a just a key 998 * (set-case). 999 * 1000 * If `element` is a nullable type and it is null an `AssertError` is thrown. 1001 * 1002 * Returns: reference to existing element if present, otherwise new `element`. 1003 * 1004 * Can be used for implementing, for instance, caching of typically strings. 1005 */ 1006 ref T insertAndReturnElement(SomeElement)(scope SomeElement element) return // template-lazy 1007 { 1008 version(LDC) pragma(inline, true); 1009 1010 assert(!keyOf(element).isNull); 1011 static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(cast(K)adjustKeyType(keyOf(element)))); } 1012 static if (borrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 1013 1014 reserveExtra(1); 1015 static if (__traits(isCopyable, SomeElement)) 1016 { 1017 const hitIndex = insertWithoutGrowthNoStatus(element); 1018 } 1019 else 1020 { 1021 const hitIndex = insertWithoutGrowthNoStatus(move(element)); 1022 } 1023 return _store[hitIndex]; 1024 } 1025 1026 /** Insert `elements`, all being either a key-value (map-case) or a just a key (set-case). 1027 */ 1028 void insertN(R)(R elements) @trusted 1029 if (isIterable!R && 1030 __traits(isCopyable, T)) // TODO support uncopyable T? 1031 { 1032 static if (borrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 1033 import std.range.primitives : hasLength; 1034 static if (hasLength!R) 1035 { 1036 reserveExtra(elements.length); // might create unused space in `_store` store 1037 } 1038 foreach (element; elements) 1039 { 1040 static if (!hasLength!R) 1041 { 1042 reserveExtra(1); 1043 } 1044 static if (hasIndirections!T) 1045 { 1046 insertWithoutGrowthNoStatus(element); 1047 } 1048 else 1049 { 1050 insertWithoutGrowthNoStatus(*cast(Unqual!T*)&element); 1051 } 1052 } 1053 } 1054 1055 /// Is `true` iff in-place rehashing during growth should be performed. 1056 enum bool growInPlaceFlag = false; // TODO warning growInPlaceWithCapacity is buggy 1057 1058 /// Numerator for grow scale. 1059 enum growScaleP = 3; 1060 /// Denominator for grow scale. 1061 enum growScaleQ = 2; 1062 1063 /** Reserve rom for `extraCapacity` number of extra buckets. */ 1064 void reserveExtra(size_t extraCapacity) // not template-lazy 1065 { 1066 version(LDC) pragma(inline, true); 1067 static if (borrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 1068 immutable newCapacity = (_count + extraCapacity)*growScaleP/growScaleQ; 1069 if (newCapacity > _store.length) 1070 { 1071 growWithNewCapacity(newCapacity); 1072 } 1073 } 1074 1075 /// Grow (rehash) to make for `newCapacity` number of elements. 1076 private void growWithNewCapacity()(size_t newCapacity) // template-lazy 1077 { 1078 version(LDC) pragma(inline, true); 1079 version(showEntries) dbg(__FUNCTION__, " newCapacity:", newCapacity); 1080 version(unittest) assert(newCapacity > _store.length); 1081 static if (__traits(hasMember, Allocator, "reallocate")) 1082 { 1083 static if (growInPlaceFlag) 1084 { 1085 growInPlaceWithCapacity(newCapacity); 1086 } 1087 else 1088 { 1089 growStandardWithNewCapacity(newCapacity); 1090 } 1091 } 1092 else 1093 { 1094 growStandardWithNewCapacity(newCapacity); 1095 } 1096 } 1097 1098 private void tagAsLazilyDeletedElementAtIndex(size_t index) 1099 { 1100 version(LDC) pragma(inline, true); 1101 1102 // key 1103 static if (useSmallLinearSearch) 1104 { 1105 if (_store.length * T.sizeof <= linearSearchMaxSize) 1106 { 1107 keyOf(_store[index]).nullify(); 1108 goto done; 1109 } 1110 } 1111 1112 static if (hasHoleableKey) 1113 { 1114 keyOf(_store[index]) = holeKeyConstant; 1115 } 1116 else 1117 { 1118 keyOf(_store[index]).nullify(); 1119 tagHoleAtIndex(index); 1120 } 1121 1122 done: 1123 1124 // value 1125 static if (hasValue) 1126 { 1127 static if (hasElaborateDestructor!V) // if we should clear all 1128 { 1129 .destroy(valueOf(_store[index])); 1130 } 1131 static if (isAddress!V) // if we should clear all 1132 { 1133 valueOf(_store[index]) = null; // please the GC 1134 } 1135 } 1136 } 1137 1138 private void insertElementAtIndex(SomeElement)(scope SomeElement element, size_t index) @trusted // template-lazy 1139 { 1140 version(LDC) pragma(inline, true); 1141 static if (isSlice!SomeElement && 1142 !is(typeof(SomeElement.init[0]) == immutable)) 1143 { 1144 /* key is an array of non-`immutable` elements which cannot safely 1145 * be stored because keys must be immutable for hashing to work 1146 * properly, therefore duplicate */ 1147 keyOf(_store[index]) = element.idup; 1148 } 1149 else 1150 { 1151 static if (__traits(isCopyable, SomeElement)) 1152 { 1153 _store[index] = element; 1154 } 1155 else 1156 { 1157 static if (__traits(isCopyable, K)) 1158 { 1159 keyOf(_store[index]) = keyOf(element); 1160 } 1161 else 1162 { 1163 move(keyOf(element), keyOf(_store[index])); 1164 } 1165 static if (hasValue) 1166 { 1167 import core.lifetime : moveEmplace; 1168 moveEmplace(valueOf(element), valueOf(_store[index])); 1169 } 1170 } 1171 } 1172 } 1173 1174 /** Rehash elements in-place. */ 1175 private void rehashInPlace()() @trusted // template-lazy 1176 { 1177 version(showEntries) dbg(__FUNCTION__); 1178 1179 import core.bitop : bts, bt; 1180 import nxt.array_help : makeZeroedBitArray, wordCountOfBitCount; 1181 1182 size_t* dones = makeZeroedBitArray!Allocator(_store.length); 1183 1184 foreach (immutable doneIndex; 0 .. _store.length) 1185 { 1186 if (bt(dones, doneIndex)) { continue; } // if _store[doneIndex] continue 1187 if (isOccupiedAtIndex(doneIndex)) 1188 { 1189 import core.lifetime : moveEmplace; 1190 T currentElement = void; 1191 1192 // TODO functionize: 1193 moveEmplace(_store[doneIndex], currentElement); 1194 static if (isInstanceOf!(Nullable, K)) 1195 { 1196 keyOf(_store[doneIndex]).nullify(); // `moveEmplace` doesn't init source of type Nullable 1197 } 1198 1199 while (true) 1200 { 1201 alias pred = (const scope index, 1202 const scope auto ref element) => (!isOccupiedAtIndex(index) || // free slot 1203 !bt(dones, index)); // or a not yet replaced element 1204 static if (usePrimeCapacity) 1205 { 1206 immutable hitIndex = xxx; 1207 } 1208 else 1209 { 1210 immutable hitIndex = _store[].triangularProbeFromIndex!(pred, assumeNonFullHaystack)(keyToIndex(keyOf(currentElement))); 1211 } 1212 assert(hitIndex != _store.length, "no free slot"); 1213 1214 bts(dones, hitIndex); // _store[hitIndex] will be at it's correct position 1215 1216 if (isOccupiedAtIndex(doneIndex)) 1217 { 1218 T nextElement = void; 1219 1220 // TODO functionize: 1221 moveEmplace(_store[hitIndex], nextElement); // save non-free slot 1222 static if (isInstanceOf!(Nullable, K)) 1223 { 1224 keyOf(_store[hitIndex]).nullify(); // `moveEmplace` doesn't init source of type Nullable 1225 } 1226 1227 moveEmplace(currentElement, _store[hitIndex]); 1228 moveEmplace(nextElement, currentElement); 1229 } 1230 else // if no free slot 1231 { 1232 moveEmplace(currentElement, _store[hitIndex]); 1233 break; // inner iteration is finished 1234 } 1235 } 1236 } 1237 bts(dones, doneIndex); // _store[doneIndex] is at it's correct position 1238 } 1239 1240 Allocator.deallocate(cast(void[])(dones[0 .. wordCountOfBitCount(_store.length)])); 1241 1242 static if (!hasHoleableKey) 1243 { 1244 clearHoles(); 1245 } 1246 } 1247 1248 /** Grow (with rehash) store in-place making room for `minimumCapacity` number of elements. 1249 */ 1250 private void growInPlaceWithCapacity()(size_t minimumCapacity) @trusted // template-lazy 1251 { 1252 assert(minimumCapacity > _store.length); 1253 1254 static if (usePrimeCapacity) 1255 { 1256 immutable newCapacity = ceilingPrime(minimumCapacity, _primeIndex); 1257 } 1258 else 1259 { 1260 immutable newCapacity = nextPow2(minimumCapacity); 1261 } 1262 1263 immutable newByteCount = T.sizeof*newCapacity; 1264 1265 const oldStorePtr = _store.ptr; 1266 immutable oldLength = _store.length; 1267 1268 auto rawStore = cast(void[])_store; 1269 if (Allocator.reallocate(rawStore, newByteCount)) 1270 { 1271 _store = cast(T[])rawStore; 1272 static if (mustAddGCRange!T) 1273 { 1274 if (oldStorePtr !is null) 1275 { 1276 gc_removeRange(oldStorePtr); // `gc_removeRange` fails for null input 1277 } 1278 gc_addRange(_store.ptr, newByteCount); 1279 } 1280 1281 static if (!hasHoleableKey) 1282 { 1283 if (_holesPtr) 1284 { 1285 _holesPtr = makeReallocatedBitArrayZeroPadded!Allocator(_holesPtr, 1286 oldLength, 1287 _store.length); 1288 } 1289 } 1290 1291 // TODO make this an array operation `nullifyAll` or `nullifyN` 1292 foreach (ref bin; _store[oldLength .. newCapacity]) 1293 { 1294 keyOf(bin).nullify(); // move this `init` to reallocate() above? 1295 } 1296 1297 rehashInPlace(); 1298 } 1299 else 1300 { 1301 assert(0, "couldn't reallocate bin"); 1302 } 1303 } 1304 1305 /** Grow (rehash) store to make room for `newCapacity` number of elements. 1306 */ 1307 private void growStandardWithNewCapacity()(size_t newCapacity) // template-lazy 1308 { 1309 version(LDC) pragma(inline, true); // LDC needs this or to prevent 10x performance regression in contains() 1310 version(showEntries) dbg(__FUNCTION__, " newCapacity:", newCapacity); 1311 version(unittest) assert(newCapacity > _store.length); 1312 auto next = typeof(this).withCapacity(newCapacity); 1313 foreach (immutable index, ref bin; _store) 1314 { 1315 if (isOccupiedAtIndex(index)) 1316 { 1317 next.insertMoveWithoutGrowth(bin); // value is zeroed but 1318 static if (!hasHoleableKey) 1319 { 1320 keyOf(bin).nullify(); // keyC must zeroed 1321 } 1322 } 1323 } 1324 move(next, this); 1325 } 1326 1327 private InsertionStatus insertWithoutGrowth(SomeElement)(const scope SomeElement element, // template-lazy 1328 out size_t hitIndex) @trusted 1329 { 1330 version(LDC) pragma(inline, true); 1331 version(unittest) 1332 { 1333 assert(!keyOf(element).isNull); 1334 static if (hasHoleableKey) { assert(!isHoleKeyConstant(adjustKeyType(keyOf(element)))); } 1335 } 1336 1337 size_t holeIndex = size_t.max; // first hole index to written to if hole found 1338 immutable hitIndexPrel = indexOfKeyOrVacancyAndFirstHole(keyOf(element), holeIndex); 1339 if (hitIndexPrel == _store.length || // keys miss and holes may have filled all empty slots 1340 keyOf(_store[hitIndexPrel]).isNull) // just key miss but a hole may have been found on the way 1341 { 1342 immutable hasHole = holeIndex != size_t.max; // hole was found along the way 1343 if (hasHole) 1344 { 1345 hitIndex = holeIndex; // pick hole instead 1346 } 1347 else 1348 { 1349 hitIndex = hitIndexPrel; // normal hit 1350 } 1351 version(unittest) assert(hitIndex != _store.length, "no null or hole slot"); 1352 static if (__traits(isCopyable, SomeElement)) 1353 { 1354 insertElementAtIndex(*cast(SomeElement*)&element, hitIndex); 1355 } 1356 else 1357 { 1358 insertElementAtIndex(move(*cast(SomeElement*)&element), hitIndex); 1359 } 1360 static if (!hasHoleableKey) 1361 { 1362 if (hasHole) { untagHoleAtIndex(hitIndex); } 1363 } 1364 _count = _count + 1; 1365 return InsertionStatus.added; 1366 } 1367 else 1368 { 1369 hitIndex = hitIndexPrel; 1370 } 1371 1372 static if (hasValue) 1373 { 1374 static if (__traits(isStaticArray, V)) 1375 { 1376 // identity comparison of static arrays implicitly coerces them 1377 // to slices, which are compared by reference, so don't use !is here 1378 immutable valueDiffers = (valueOf(element) != 1379 valueOf(_store[hitIndexPrel])); // only value changed 1380 } 1381 else 1382 { 1383 immutable valueDiffers = (valueOf(element) !is 1384 valueOf(_store[hitIndexPrel])); // only value changed 1385 } 1386 if (valueDiffers) // only value changed 1387 { 1388 move(valueOf(*cast(SomeElement*)&element), 1389 valueOf(_store[hitIndexPrel])); // value is defined so overwrite it 1390 return InsertionStatus.modified; 1391 } 1392 } 1393 return InsertionStatus.unmodified; 1394 } 1395 1396 private size_t insertWithoutGrowthNoStatus(SomeElement)(const scope SomeElement element) @trusted // template-lazy 1397 { 1398 version(LDC) pragma(inline, true); 1399 version(unittest) 1400 { 1401 assert(!keyOf(element).isNull); 1402 static if (hasHoleableKey) { assert(!isHoleKeyConstant(adjustKeyType(keyOf(element)))); } 1403 } 1404 1405 size_t hitIndex = 0; 1406 size_t holeIndex = size_t.max; // first hole index to written to if hole found 1407 immutable hitIndexPrel = indexOfKeyOrVacancyAndFirstHole(adjustKeyType(keyOf(element)), holeIndex); 1408 if (hitIndexPrel == _store.length || // keys miss and holes may have filled all empty slots 1409 keyOf(_store[hitIndexPrel]).isNull) // just key miss but a hole may have been found on the way 1410 { 1411 immutable hasHole = holeIndex != size_t.max; // hole was found along the way 1412 if (hasHole) 1413 { 1414 hitIndex = holeIndex; // pick hole instead 1415 } 1416 else 1417 { 1418 hitIndex = hitIndexPrel; // normal hit 1419 } 1420 version(unittest) assert(hitIndex != _store.length, "no null or hole slot"); 1421 static if (__traits(isCopyable, SomeElement)) 1422 { 1423 insertElementAtIndex(*cast(SomeElement*)&element, hitIndex); 1424 } 1425 else 1426 { 1427 insertElementAtIndex(move(*cast(SomeElement*)&element), hitIndex); 1428 } 1429 static if (!hasHoleableKey) 1430 { 1431 if (hasHole) { untagHoleAtIndex(hitIndex); } 1432 } 1433 _count = _count + 1; 1434 return hitIndex; 1435 } 1436 else 1437 { 1438 hitIndex = hitIndexPrel; 1439 } 1440 1441 static if (hasValue) 1442 { 1443 // modify existing value 1444 move(valueOf(*cast(SomeElement*)&element), 1445 valueOf(_store[hitIndexPrel])); // value is defined so overwrite it 1446 } 1447 1448 return hitIndex; 1449 } 1450 1451 /** Insert `element`, being either a key-value (map-case) or a just a key (set-case). 1452 */ 1453 private InsertionStatus insertMoveWithoutGrowth()(ref T element) // template-lazy 1454 { 1455 version(LDC) pragma(inline, true); 1456 size_t hitIndex = 0; 1457 return insertWithoutGrowth(move(element), hitIndex); 1458 } 1459 1460 static if (hasValue) 1461 { 1462 /** Insert or replace `value` at `key`. */ 1463 InsertionStatus insert()(K key, V value) // template-lazy 1464 { 1465 pragma(inline, true); // LDC must have this 1466 static if (__traits(isCopyable, K)) 1467 { 1468 static if (__traits(isCopyable, V)) 1469 { 1470 return insert(T(key, value)); 1471 } 1472 else 1473 { 1474 return insert(T(key, move(value))); 1475 } 1476 } 1477 else 1478 { 1479 static if (__traits(isCopyable, V)) 1480 { 1481 return insert(T(move(key), value)); 1482 } 1483 else 1484 { 1485 return insert(T(move(key), move(value))); 1486 } 1487 } 1488 } 1489 } 1490 1491 static if (!hasValue) // HashSet 1492 { 1493 scope const(K)* opBinaryRight(string op, SomeKey)(const scope SomeKey key) const return @trusted 1494 if (op == `in` && 1495 isScopedKeyType!(typeof(key))) 1496 { 1497 pragma(inline, true); 1498 1499 assert(!key.isNull); 1500 static if (hasHoleableKey) { assert(!isHoleKeyConstant(cast(K)adjustKeyType(key))); } 1501 1502 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKeyType(key)); // cast scoped `key` is @trusted 1503 if (hitIndex != _store.length && 1504 isOccupiedAtIndex(hitIndex)) 1505 { 1506 return &_store[hitIndex]; 1507 } 1508 else 1509 { 1510 return null; 1511 } 1512 } 1513 1514 ref typeof(this) opOpAssign(string op, SomeKey)(const scope SomeKey key) return @trusted 1515 if (op == `~` && // binary assignment operator `~=` 1516 isScopedKeyType!(typeof(key))) 1517 { 1518 version(LDC) pragma(inline, true); 1519 reserveExtra(1); 1520 const hitIndex = insertWithoutGrowthNoStatus(key); 1521 return this; 1522 } 1523 1524 /** Try to retrieve `class`-element of type `Class` constructed with 1525 * parameters `params`. 1526 * 1527 * Typically used to implement (polymorphic) caching of class-types 1528 * without the need for GG-allocating a temporary instance of a 1529 * `class`-element potentially already stored in `this` set. 1530 * 1531 * Polymorphic caching can be realized by setting `hasher` to 1532 * `hash_functions.hashOfPolymorphic`. 1533 */ 1534 scope const(Class) tryGetElementFromCtorParams(Class, Params...)(scope Params params) const return @trusted 1535 if (is(Class : K)) 1536 { 1537 import core.lifetime : emplace; 1538 void[__traits(classInstanceSize, Class)] tempNode_ = void; 1539 scope Class temp = emplace!(Class)(tempNode_, params); 1540 Class* hit = cast(Class*)(temp in this); 1541 static if (__traits(hasMember, Class, "__dtor")) 1542 { 1543 temp.__dtor(); 1544 } 1545 if (hit) 1546 { 1547 auto typedHit = cast(typeof(return))*hit; 1548 assert(typedHit, "Expected class " ~ Class.stringof ~ " but got hit was of other type"); // TODO give warning or throw 1549 return typedHit; 1550 } 1551 return null; 1552 } 1553 } 1554 1555 static if (hasValue) // HashMap 1556 { 1557 scope inout(V)* opBinaryRight(string op, SomeKey)(const scope SomeKey key) inout return @trusted // `auto ref` here makes things slow 1558 if (op == `in` && 1559 isScopedKeyType!(SomeKey)) 1560 { 1561 version(LDC) pragma(inline, true); 1562 // pragma(msg, SomeKey, " => ", K); 1563 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(cast(const(K))adjustKeyType(key)); // cast scoped `key` is @trusted 1564 if (hitIndex != _store.length && 1565 isOccupiedAtIndex(hitIndex)) 1566 { 1567 return cast(typeof(return))&_store[hitIndex].value; 1568 } 1569 else 1570 { 1571 return null; 1572 } 1573 } 1574 1575 /// Indexing. 1576 scope ref inout(V) opIndex(SomeKey)(const scope SomeKey key) inout return @trusted // `auto ref` here makes things slow. TODO @nogc 1577 if (isScopedKeyType!(typeof(key))) 1578 { 1579 version(LDC) pragma(inline, true); 1580 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKeyType(key)); // cast scoped `key` is @trusted 1581 if (hitIndex != _store.length && 1582 isOccupiedAtIndex(hitIndex)) 1583 { 1584 return _store[hitIndex].value; 1585 } 1586 import core.exception : RangeError; 1587 throw new RangeError("Key not found"); // TODO use assert instead? 1588 } 1589 1590 /** Get value of `key` or `defaultValue` if `key` not present (and 1591 * therefore `nothrow`). 1592 * 1593 * Returns: value reference iff `defaultValue` is an l-value. 1594 * 1595 * TODO make `defaultValue` `lazy` when that can be `nothrow` 1596 */ 1597 auto ref inout(V) get()(const scope K key, // template-lazy 1598 auto ref inout(V) defaultValue) inout 1599 { 1600 version(LDC) pragma(inline, true); 1601 if (auto valuePtr = key in this) 1602 { 1603 return *valuePtr; 1604 } 1605 else 1606 { 1607 return defaultValue; 1608 } 1609 } 1610 1611 /** Get reference to `key`-part of stored element at `key`, if present, 1612 * otherwise return `defaultKey`. 1613 * 1614 * Used to implement caching inside the key part of a map. 1615 */ 1616 ref const(K) getKeyRef(SomeKey)(const scope SomeKey key, // template-lazy 1617 ref const(K) defaultKey) const return @trusted @nogc 1618 if (isScopedKeyType!(SomeKey)) 1619 { 1620 version(LDC) pragma(inline, true); 1621 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKeyType(key)); // cast scoped `key` is @trusted 1622 if (hitIndex != _store.length && 1623 isOccupiedAtIndex(hitIndex)) 1624 { 1625 return _store[hitIndex].key; 1626 } 1627 else 1628 { 1629 return defaultKey; 1630 } 1631 } 1632 1633 /** Supports the syntax `aa[key] = value;`. 1634 */ 1635 ref V opIndexAssign()(V value, K key) // template-lazy. TODO return scope 1636 { 1637 version(LDC) pragma(inline, true); 1638 assert(!key.isNull); 1639 static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(key)); } 1640 static if (borrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 1641 reserveExtra(1); 1642 static if (__traits(isCopyable, K)) 1643 { 1644 static if (__traits(isCopyable, V)) 1645 { 1646 const hitIndex = insertWithoutGrowthNoStatus(T(key, value)); 1647 } 1648 else 1649 { 1650 const hitIndex = insertWithoutGrowthNoStatus(T(key, move(value))); 1651 } 1652 } 1653 else 1654 { 1655 static if (__traits(isCopyable, V)) 1656 { 1657 const hitIndex = insertWithoutGrowthNoStatus(T(move(key), value)); 1658 } 1659 else 1660 { 1661 const hitIndex = insertWithoutGrowthNoStatus(T(move(key), move(value))); 1662 } 1663 } 1664 return _store[hitIndex].value; 1665 } 1666 1667 ref V opIndexOpAssign(string op, Rhs)(Rhs rhs, K key) // TODO return scope 1668 // if (true) // TODO pre-check that mixin will work 1669 { 1670 // pragma(msg, "opIndexOpAssign: Key:", K, " Value:", V, " Rhs:", Rhs, " op:", op); 1671 assert(!key.isNull); 1672 static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(key)); } 1673 static if (borrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 1674 1675 reserveExtra(1); 1676 1677 size_t holeIndex = size_t.max; // first hole index to written to if hole found 1678 immutable hitIndex = indexOfKeyOrVacancyAndFirstHole(key, holeIndex); 1679 if (hitIndex == _store.length || // keys miss and holes may have filled all empty slots 1680 keyOf(_store[hitIndex]).isNull) // just key miss but a hole may have been found on the way 1681 { 1682 immutable hasHole = holeIndex != size_t.max; // hole was found along the way 1683 const index = (hasHole ? 1684 holeIndex : // pick hole instead 1685 hitIndex); // normal hit 1686 version(unittest) assert(index != _store.length, "no null or hole slot"); 1687 static if (__traits(isCopyable, K)) 1688 { 1689 static if (op == "~" || 1690 op == "+" || 1691 op == "*") 1692 { 1693 static if (is(V : Rhs[])) 1694 { 1695 insertElementAtIndex(T(key, [rhs]), // TODO if `V(rhs)` is not supported use `V.init` followed by `OP= rhs` 1696 index); 1697 } 1698 else 1699 { 1700 // dbg("opIndexOpAssign-new: k:", key, " rhs:", rhs); 1701 insertElementAtIndex(T(key, V(rhs)), // TODO if `V(rhs)` is not supported use `V.init` followed by `OP= rhs` 1702 index); 1703 } 1704 } 1705 else 1706 { 1707 static assert(0, "Handel op " ~ op); 1708 } 1709 } 1710 else 1711 { 1712 static assert(0, "Handle uncopyable key " ~ K.stringof); 1713 // insertElementAtIndex(move(*cast(SomeElement*)&element), index); 1714 } 1715 static if (!hasHoleableKey) 1716 { 1717 if (hasHole) { untagHoleAtIndex(index); } 1718 } 1719 _count = _count + 1; 1720 return _store[index].value; 1721 } 1722 else // `key`-hit at index `hitIndex` 1723 { 1724 // dbg("opIndexOpAssign-mod: k:", key, " rhs:", rhs); 1725 mixin(`return _store[hitIndex].value ` ~ op ~ `= rhs;`); // modify existing value 1726 } 1727 } 1728 } 1729 1730 /** Remove `element`. 1731 * Returns: `true` if element was removed, `false` otherwise. 1732 */ 1733 bool remove(SomeKey)(const scope SomeKey key) scope // template-lazy 1734 if (isScopedKeyType!(typeof(key))) 1735 { 1736 version(LDC) pragma(inline, true); 1737 static if (borrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 1738 1739 static if (useSmallLinearSearch) 1740 { 1741 if (_store.length * T.sizeof <= linearSearchMaxSize) 1742 { 1743 foreach (const index, const ref element; _store) // linear search is faster for small arrays 1744 { 1745 if (keyEqualPredFn(keyOf(element), key)) 1746 { 1747 tagAsLazilyDeletedElementAtIndex(index); 1748 _count = _count - 1; 1749 return true; 1750 } 1751 } 1752 return false; 1753 } 1754 } 1755 1756 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(cast(const(K))adjustKeyType(key)); 1757 if (hitIndex != _store.length && 1758 isOccupiedAtIndex(hitIndex)) 1759 { 1760 tagAsLazilyDeletedElementAtIndex(hitIndex); 1761 _count = _count - 1; 1762 return true; 1763 } 1764 return false; 1765 } 1766 1767 /** Remove all elements matching `keys` followed by a rehash. 1768 * 1769 * Returns: number of elements that were removed. 1770 */ 1771 version(none) 1772 { 1773 import nxt.traits_ex : isRefIterable; 1774 import std.range.primitives : front; 1775 1776 size_t rehashingRemoveN(Keys)(const scope Keys keys) // template-lazy 1777 if (isRefIterable!Keys && 1778 is(typeof(Keys.front == K.init))) 1779 { 1780 static if (borrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 1781 rehash!("!a.isNull && keys.canFind(a)")(); // TODO make this work 1782 return 0; 1783 } 1784 } 1785 1786 /// Check if empty. 1787 @property bool empty() const { return _count == 0; } 1788 1789 /// Get length (read-only). 1790 @property size_t length() const { return _count; } 1791 1792 /// Get bin count. 1793 @property size_t binCount() const { return _store.length; } 1794 1795 /** Returns: get total probe count for all elements stored. */ 1796 size_t totalProbeCount()() const // template-lazy 1797 { 1798 version(LDC) pragma(inline, true); // LDC needs this or to prevent 10x performance regression in contains() 1799 static if (hasValue) 1800 { 1801 auto range = byKeyValue(this); 1802 } 1803 else 1804 { 1805 auto range = byElement(this); 1806 } 1807 typeof(return) totalCount = 0; 1808 foreach (const ref currentElement; range) 1809 { 1810 static if (__traits(isCopyable, T)) 1811 { 1812 /* don't use `auto ref` for copyable `T`'s to prevent 1813 * massive performance drop for small elements when compiled 1814 * with LDC. TODO remove when LDC is fixed. */ 1815 alias pred = (const scope element) => (keyEqualPredFn(keyOf(element), 1816 keyOf(currentElement))); 1817 } 1818 else 1819 { 1820 alias pred = (const scope auto ref element) => (keyEqualPredFn(keyOf(element), 1821 keyOf(currentElement))); 1822 } 1823 static if (usePrimeCapacity) 1824 { 1825 const probeCount = xxx; 1826 } 1827 else 1828 { 1829 const probeCount = triangularProbeCountFromIndex!(pred)(_store[], keyToIndex(keyOf(currentElement))); 1830 } 1831 1832 totalCount += probeCount; 1833 } 1834 return totalCount; 1835 } 1836 1837 /** Returns: average probe count for all elements stored. */ 1838 double averageProbeCount()() const // template-lazy 1839 { 1840 return cast(typeof(return))totalProbeCount/length; 1841 } 1842 1843 /** Unsafe access to raw store. 1844 * 1845 * Needed by wrapper containers such as SSOOpenHashSet. 1846 */ 1847 inout(T)[] rawStore() inout @system pure nothrow @nogc 1848 { 1849 pragma(inline, true); 1850 return _store; 1851 } 1852 1853 static if (hasHoleableKey) 1854 { 1855 static bool isOccupiedBin(const ref T bin) 1856 { 1857 pragma(inline, true); 1858 if (keyOf(bin).isNull) { return false; } 1859 return !isHoleKeyConstant(keyOf(bin)); 1860 } 1861 } 1862 1863 private: 1864 static if (hasFunctionAttributes!(Allocator.allocate, "@nogc")) 1865 { 1866 import nxt.gc_traits : NoGc; 1867 @NoGc T[] _store; // one element per bin 1868 } 1869 else 1870 { 1871 T[] _store; // one element per bin 1872 } 1873 static if (usePrimeCapacity) 1874 { 1875 PrimeIndex _primeIndex = PrimeIndex.init; 1876 } 1877 1878 static if (!hasHoleableKey) 1879 { 1880 static if (hasFunctionAttributes!(Allocator.allocate, "@nogc")) 1881 { 1882 @NoGc size_t* _holesPtr; // bit array describing which bin elements that has been removed (holes) 1883 } 1884 else 1885 { 1886 size_t* _holesPtr; // bit array describing which bin elements that has been removed (holes) 1887 } 1888 } 1889 1890 static if (borrowChecked) 1891 { 1892 debug // use Rust-style borrow checking at run-time 1893 { 1894 size_t _count; 1895 size_t _borrowCount; 1896 1897 /// Number of bits needed to store number of read borrows. 1898 enum borrowCountBits = 8*borrowChecked.sizeof; 1899 1900 /// Maximum value possible for `_borrowCount`. 1901 enum borrowCountMax = 2^^borrowCountBits - 1; 1902 1903 version(none) 1904 { 1905 /// Number of bits needed to store number of read borrows. 1906 enum borrowCountBits = 24; 1907 1908 /// Maximum value possible for `_borrowCount`. 1909 enum borrowCountMax = 2^^borrowCountBits - 1; 1910 1911 import std.bitmanip : bitfields; 1912 mixin(bitfields!(size_t, "_count", 8*size_t.sizeof - borrowCountBits, 1913 uint, "_borrowCount", borrowCountBits, 1914 )); 1915 } 1916 1917 pragma(inline, true): 1918 @safe pure nothrow @nogc: 1919 1920 @property 1921 { 1922 /// Returns: `true` iff `this` is borrowed (either read or write). 1923 bool isBorrowed() const { return _borrowCount >= 1; } 1924 1925 /// Returns: number of borrowers of `this` (both read and write). 1926 auto borrowCount() const { return _borrowCount; } 1927 } 1928 1929 /// Increase borrow count. 1930 void incBorrowCount() 1931 { 1932 assert(_borrowCount + 1 != borrowCountMax); 1933 _borrowCount = _borrowCount + 1; 1934 } 1935 1936 /// Decrease borrow count. 1937 void decBorrowCount() 1938 { 1939 assert(_borrowCount != 0); 1940 _borrowCount = _borrowCount - 1; 1941 } 1942 } 1943 else 1944 { 1945 size_t _count; // total number of non-null elements stored in `_store` 1946 } 1947 } 1948 else 1949 { 1950 size_t _count; // total number of non-null elements stored in `_store` 1951 } 1952 1953 /** Returns: bin index of `key`. */ 1954 private size_t keyToIndex(SomeKey)(const scope SomeKey key) const @trusted 1955 { 1956 version(LDC) pragma(inline, true); 1957 1958 /** Returns: current index mask from bin count `_store.length`. */ 1959 static size_t powerOf2Mask(in size_t length) 1960 { 1961 version(unittest) {} 1962 else 1963 { 1964 pragma(inline, true); 1965 } 1966 immutable typeof(return) mask = length - 1; 1967 version(unittest) 1968 { 1969 debug import std.math : isPowerOf2; 1970 debug assert(isPowerOf2(length)); 1971 } 1972 return mask; 1973 } 1974 1975 static if (is(typeof(hasher(key)) == hash_t)) // for instance when hasher being `hashOf` 1976 { 1977 const size_t hash = hasher(key); 1978 } 1979 else static if (is(hasher == struct) || // such as `FNV` 1980 is(hasher == class)) 1981 { 1982 import nxt.digestion : hashOf2; 1983 const size_t hash = hashOf2!(hasher)(key); 1984 } 1985 else 1986 { 1987 static assert(false, "Unsupported hasher of type " ~ typeof(hasher).stringof); 1988 } 1989 1990 static if (usePrimeCapacity) 1991 { 1992 return moduloPrimeIndex(hash, _primeIndex); 1993 } 1994 else 1995 { 1996 return hash & powerOf2Mask(_store.length); 1997 } 1998 } 1999 2000 /** Find index to `key` if it exists or to first empty slot found, skipping 2001 * (ignoring) lazily deleted slots. 2002 */ 2003 private size_t indexOfKeyOrVacancySkippingHoles(const scope K key) const @trusted scope // `auto ref` here makes things slow 2004 // TODO if (...) 2005 { 2006 version(LDC) pragma(inline, true); 2007 version(unittest) 2008 { 2009 assert(!key.isNull); 2010 static if (hasHoleableKey) { assert(!isHoleKeyConstant(key)); } 2011 } 2012 2013 static if (useSmallLinearSearch) 2014 { 2015 if (_store.length * T.sizeof <= linearSearchMaxSize) 2016 { 2017 foreach (const index, const ref element; _store) // linear search is faster for small arrays 2018 { 2019 if ((keyOf(element).isNull || 2020 keyEqualPredFn(keyOf(element), key))) 2021 { 2022 return index; 2023 } 2024 } 2025 return _store.length; 2026 } 2027 } 2028 2029 static if (hasHoleableKey) 2030 { 2031 alias pred = (const scope auto ref element) => (keyOf(element).isNull || 2032 keyEqualPredFn(keyOf(element), key)); 2033 } 2034 else 2035 { 2036 alias pred = (const scope index, 2037 const scope auto ref element) => (!hasHoleAtPtrIndex(_holesPtr, index) && 2038 (keyOf(element).isNull || 2039 keyEqualPredFn(keyOf(element), key))); 2040 } 2041 2042 static if (usePrimeCapacity) 2043 { 2044 return xxx; 2045 } 2046 else 2047 { 2048 return _store[].triangularProbeFromIndex!(pred, assumeNonFullHaystack)(keyToIndex(key)); 2049 } 2050 } 2051 2052 private size_t indexOfKeyOrVacancyAndFirstHole(const scope K key, // `auto ref` here makes things slow 2053 ref size_t holeIndex) const @trusted scope 2054 { 2055 version(LDC) pragma(inline, true); 2056 version(unittest) 2057 { 2058 assert(!key.isNull); 2059 static if (hasHoleableKey) { assert(!isHoleKeyConstant(key)); } 2060 } 2061 2062 static if (useSmallLinearSearch) 2063 { 2064 if (_store.length * T.sizeof <= linearSearchMaxSize) 2065 { 2066 foreach (const index, const ref element; _store) // linear search is faster for small arrays 2067 { 2068 if ((keyOf(element).isNull || 2069 keyEqualPredFn(keyOf(element), key))) 2070 { 2071 return index; 2072 } 2073 } 2074 return _store.length; 2075 } 2076 2077 } 2078 2079 static if (hasHoleableKey) 2080 { 2081 alias hitPred = (const scope auto ref element) => (keyOf(element).isNull || 2082 keyEqualPredFn(keyOf(element), key)); 2083 alias holePred = (const scope auto ref element) => (isHoleKeyConstant(keyOf(element))); 2084 } 2085 else 2086 { 2087 alias hitPred = (const scope index, 2088 const scope auto ref element) => (!hasHoleAtPtrIndex(_holesPtr, index) && 2089 (keyOf(element).isNull || 2090 keyEqualPredFn(keyOf(element), key))); 2091 alias holePred = (const scope index, // TODO use only index 2092 const scope auto ref element) => (hasHoleAtPtrIndex(_holesPtr, index)); 2093 } 2094 2095 static if (usePrimeCapacity) 2096 { 2097 return xxx; 2098 } 2099 else 2100 { 2101 return _store[].triangularProbeFromIndexIncludingHoles!(hitPred, holePred, assumeNonFullHaystack)(keyToIndex(key), holeIndex); 2102 } 2103 } 2104 2105 /** Returns: `true` iff `index` indexes a non-null element, `false` 2106 * otherwise. 2107 */ 2108 private bool isOccupiedAtIndex(size_t index) const 2109 { 2110 version(LDC) pragma(inline, true); 2111 version(unittest) assert(index < _store.length); 2112 if (keyOf(_store[index]).isNull) { return false; } 2113 static if (hasHoleableKey) 2114 { 2115 return !isHoleKeyConstant(keyOf(_store[index])); 2116 } 2117 else 2118 { 2119 return !hasHoleAtPtrIndex(_holesPtr, index); 2120 } 2121 } 2122 } 2123 2124 /** Duplicate `src` into uninitialized `dst` ignoring prior destruction of `dst`. 2125 * TODO move to more generic place 2126 */ 2127 static private void duplicateEmplace(T)(const scope ref T src, 2128 scope ref T dst) @system 2129 { 2130 pragma(inline, true); 2131 import core.internal.traits : hasElaborateCopyConstructor; 2132 import std.traits : isBasicType, isInstanceOf; 2133 static if (!hasElaborateCopyConstructor!T) 2134 { 2135 import std.typecons : Nullable; 2136 static if (is(T == class) || 2137 is(T == string)) 2138 { 2139 dst = cast(T)src; 2140 } 2141 else static if (isBasicType!T || 2142 isInstanceOf!(Nullable, T)) // `Nullable` types cannot be emplaced 2143 { 2144 dst = src; 2145 } 2146 else // TODO can this case occur? 2147 { 2148 import core.internal.traits : Unqual; 2149 import core.lifetime : emplace; 2150 emplace(&dst, cast(Unqual!T)src); 2151 } 2152 } 2153 else static if (__traits(hasMember, T, "dup")) 2154 { 2155 import core.lifetime : emplace; 2156 // TODO when `emplace` can handle src being an r-value of uncopyable types replace with: `emplace(&dst, src.dup);` 2157 emplace(&dst); 2158 dst = src.dup; 2159 } 2160 else 2161 { 2162 debug static assert(0, "cannot duplicate a " ~ T.stringof); 2163 } 2164 } 2165 2166 /** L-value element reference (and in turn range iterator). 2167 */ 2168 static private struct LvalueElementRef(SomeMap) 2169 { 2170 import std.traits : isMutable; 2171 debug static assert(isMutable!SomeMap, "SomeMap type should always be mutable"); 2172 2173 private SomeMap* _table; // scoped access 2174 private size_t _binIndex; // index to bin inside `table` 2175 private size_t _hitCounter; // counter over number of elements popped (needed for length) 2176 2177 this(SomeMap* table) @trusted 2178 { 2179 pragma(inline, true); 2180 this._table = table; 2181 static if (SomeMap.isBorrowChecked) 2182 { 2183 debug 2184 { 2185 _table.incBorrowCount(); 2186 } 2187 } 2188 } 2189 2190 ~this() @nogc @trusted 2191 { 2192 pragma(inline, true); 2193 static if (SomeMap.isBorrowChecked) 2194 { 2195 debug 2196 { 2197 _table.decBorrowCount(); 2198 } 2199 } 2200 } 2201 2202 this(this) @trusted 2203 { 2204 pragma(inline, true); 2205 static if (SomeMap.isBorrowChecked) 2206 { 2207 debug 2208 { 2209 assert(_table._borrowCount != 0); 2210 _table.incBorrowCount(); 2211 } 2212 } 2213 } 2214 2215 /// Check if empty. 2216 @property bool empty() const @safe pure nothrow @nogc 2217 { 2218 pragma(inline, true); 2219 return _binIndex == _table.binCount; 2220 } 2221 2222 /// Get number of element left to pop. 2223 @property size_t length() const @safe pure nothrow @nogc 2224 { 2225 pragma(inline, true); 2226 return _table.length - _hitCounter; 2227 } 2228 2229 @property typeof(this) save() // ForwardRange 2230 { 2231 pragma(inline, true); 2232 return this; 2233 } 2234 2235 void popFront() 2236 { 2237 version(LDC) pragma(inline, true); 2238 assert(!empty); 2239 _binIndex += 1; 2240 findNextNonEmptyBin(); 2241 _hitCounter += 1; 2242 } 2243 2244 private void findNextNonEmptyBin() 2245 { 2246 pragma(inline, true); 2247 while (_binIndex != (*_table).binCount && 2248 !(*_table).isOccupiedAtIndex(_binIndex)) 2249 { 2250 _binIndex += 1; 2251 } 2252 } 2253 } 2254 2255 /** R-value element reference (and in turn range iterator). 2256 * 2257 * Does need to do borrow-checking. 2258 */ 2259 static private struct RvalueElementRef(SomeMap) 2260 { 2261 import std.traits : isMutable; 2262 debug static assert(isMutable!SomeMap, "SomeMap type should always be mutable"); 2263 2264 SomeMap _table; // owned table 2265 size_t _binIndex; // index to bin inside table 2266 size_t _hitCounter; // counter over number of elements popped 2267 2268 /// Check if empty. 2269 @property bool empty() const @safe pure nothrow @nogc 2270 { 2271 pragma(inline, true); 2272 return _binIndex == _table.binCount; 2273 } 2274 2275 /// Get number of element left to pop. 2276 @property size_t length() const @safe pure nothrow @nogc 2277 { 2278 pragma(inline, true); 2279 return _table.length - _hitCounter; 2280 } 2281 2282 void popFront() 2283 { 2284 version(LDC) pragma(inline, true); 2285 assert(!empty); 2286 _binIndex += 1; 2287 findNextNonEmptyBin(); 2288 _hitCounter += 1; 2289 } 2290 2291 private void findNextNonEmptyBin() 2292 { 2293 pragma(inline, true); 2294 while (_binIndex != _table.binCount && 2295 !_table.isOccupiedAtIndex(_binIndex)) 2296 { 2297 _binIndex += 1; 2298 } 2299 } 2300 } 2301 2302 /** Hash set with in-place open-addressing, storing keys (elements) of type `K`. 2303 * 2304 * Reuse `OpenHashMap` with its V-type set to `void`. 2305 * 2306 * See_Also: `OpenHashMap`. 2307 */ 2308 alias OpenHashSet(K, 2309 alias hasher = hashOf, 2310 string keyEqualPred = defaultKeyEqualPredOf!K, 2311 alias Allocator = Mallocator.instance, 2312 bool borrowChecked = false, 2313 bool useSmallLinearSearch = true, 2314 bool usePrimeCapacity = false) = 2315 OpenHashMap!(K, void, hasher, keyEqualPred, Allocator, borrowChecked, useSmallLinearSearch, usePrimeCapacity); 2316 2317 import std.traits : isInstanceOf; 2318 import std.functional : unaryFun; 2319 2320 /** Remove all elements in `x` matching `pred`. 2321 * 2322 * TODO make this generic for all iterable containers and move to 2323 * container_algorithm. 2324 */ 2325 size_t removeAllMatching(alias pred, SomeMap)(auto ref SomeMap x) @trusted 2326 if (isInstanceOf!(OpenHashMap, SomeMap) && // TODO generalize to `isSetOrMap` 2327 is(typeof((unaryFun!pred)))) 2328 { 2329 import nxt.nullable_traits : nullify; 2330 size_t removalCount = 0; 2331 foreach (immutable index, ref bin; x._store) 2332 { 2333 // TODO: 2334 // move to SomeMap.removeRef(bin) // uses: `offset = &bin - _store.ptr` 2335 // move to SomeMap.inplaceRemove(bin) // uses: `offset = &bin - _store.ptr` 2336 // or to SomeMap.removeAtIndex(index) 2337 if (x.isOccupiedAtIndex(index) && 2338 unaryFun!pred(bin)) 2339 { 2340 x.tagAsLazilyDeletedElementAtIndex(index); 2341 removalCount += 1; 2342 } 2343 } 2344 x._count = x._count - removalCount; 2345 return removalCount; // TODO remove this return value 2346 } 2347 2348 /** Returns: `x` eagerly filtered on `pred`. 2349 TODO move to container_algorithm.d with more generic template restrictions 2350 */ 2351 SomeMap filtered(alias pred, SomeMap)(SomeMap x) 2352 if (isInstanceOf!(OpenHashMap, SomeMap)) // TODO generalize to `isSetOrMap` 2353 { 2354 import core.lifetime : move; 2355 import std.functional : not; 2356 x.removeAllMatching!(not!pred); // `x` is a singleton (r-value) so safe to mutate 2357 return move(x); // functional 2358 } 2359 2360 /** Returns: `x` eagerly intersected with `y`. 2361 TODO move to container_algorithm.d. 2362 */ 2363 auto intersectedWith(C1, C2)(C1 x, auto ref C2 y) 2364 if (isInstanceOf!(OpenHashMap, C1) && // TODO generalize to `isSetOrMap` 2365 isInstanceOf!(OpenHashMap, C2)) // TODO generalize to `isSetOrMap` 2366 { 2367 import core.lifetime : move; 2368 static if (__traits(isRef, y)) // y is l-value 2369 { 2370 // @("complexity", "O(x.length)") 2371 return move(x).filtered!(_ => y.contains(_)); // only x can be reused 2372 } 2373 else 2374 { 2375 /* both are r-values so reuse the shortest */ 2376 // @("complexity", "O(min(x.length), min(y.length))") 2377 if (x.length < 2378 y.length) 2379 { 2380 return move(x).filtered!(_ => y.contains(_)); // functional 2381 } 2382 else 2383 { 2384 return move(y).filtered!(_ => x.contains(_)); // functional 2385 } 2386 } 2387 } 2388 2389 /// exercise opEquals 2390 @safe pure nothrow @nogc 2391 unittest 2392 { 2393 import std.typecons : Nullable; 2394 import nxt.digestx.fnv : FNV; 2395 2396 alias K = Nullable!(ulong, ulong.max); 2397 alias X = OpenHashSet!(K, FNV!(64, true)); 2398 2399 const n = 100; 2400 2401 X a; 2402 foreach (const i_; 0 .. n) 2403 { 2404 const i = 1113*i_; // insert in order 2405 assert(!a.contains(K(i))); 2406 assert(!a.containsUsingLinearSearch(K(i))); 2407 assert(a.insertAndReturnElement(K(i)) == K(i)); 2408 assert(a.contains(K(i))); 2409 assert(a.containsUsingLinearSearch(K(i))); 2410 } 2411 2412 X b; 2413 foreach (const i_; 0 .. n) 2414 { 2415 const i = 1113*(n - 1 - i_); // insert in reverse 2416 assert(!b.contains(K(i))); 2417 assert(!b.containsUsingLinearSearch(K(i))); 2418 assert(b.insertAndReturnElement(K(i)) == K(i)); 2419 assert(b.contains(K(i))); 2420 assert(b.containsUsingLinearSearch(K(i))); 2421 } 2422 2423 assert(a == b); 2424 2425 // bin storage must be deterministic 2426 () @trusted { assert(a._store != b._store); }(); 2427 } 2428 2429 @safe pure nothrow @nogc unittest 2430 { 2431 import nxt.digestx.fnv : FNV; 2432 2433 enum Pot { noun, verb } 2434 struct ExprPot 2435 { 2436 string expr; 2437 Pot pot; 2438 2439 alias nullifier = expr; 2440 static immutable nullValue = typeof(this).init; 2441 } 2442 2443 alias X = OpenHashSet!(ExprPot, FNV!(64, true)); 2444 2445 X x; 2446 2447 const aa = "aa"; 2448 2449 // two keys with same contents but different place in memory 2450 const key1 = ExprPot(aa[0 .. 1], Pot.noun); 2451 const key2 = ExprPot(aa[1 .. 2], Pot.noun); 2452 2453 assert(key1 == key2); 2454 assert(key1 !is key2); 2455 2456 assert(!x.contains(key1)); 2457 assert(!x.contains(key2)); 2458 x.insert(key1); 2459 assert(x.contains(key1)); 2460 assert(x.containsUsingLinearSearch(key1)); 2461 assert(x.contains(key2)); 2462 /* assert(x.containsUsingLinearSearch(key2)); */ 2463 assert(key1 in x); 2464 assert(key2 in x); 2465 } 2466 2467 /// `string` as key 2468 @safe pure nothrow @nogc unittest 2469 { 2470 version(showEntries) dbg(); 2471 import nxt.container_traits : mustAddGCRange; 2472 import nxt.digestx.fnv : FNV; 2473 2474 alias X = OpenHashSet!(string, FNV!(64, true)); 2475 debug static assert(!mustAddGCRange!X); 2476 debug static assert(X.sizeof == 24); // dynamic arrays also `hasAddressLikeKey` 2477 2478 auto x = X(); 2479 2480 auto testEscapeShouldFail()() @safe pure 2481 { 2482 X x; 2483 x.insert("a"); 2484 return x.byElement; 2485 } 2486 2487 auto testEscapeShouldFailFront()() @safe pure 2488 { 2489 X x; 2490 x.insert("a"); 2491 return x.byElement.front; 2492 } 2493 2494 assert(&"a"[0] is &"a"[0]); // string literals are store in common place 2495 2496 const aa = "aa"; 2497 2498 // string slices are equal when elements are equal regardless of position 2499 // (.ptr) in memory 2500 assert(x.insertAndReturnElement(aa[0 .. 1]) !is "a"); 2501 x.insert(aa[0 .. 1]); 2502 assert(x.insertAndReturnElement(aa[0 .. 1]) is aa[0 .. 1]); 2503 assert(x.contains(aa[1 .. 2])); 2504 assert(x.containsUsingLinearSearch(aa[1 .. 2])); 2505 2506 const(char)[] aa_ = "aa"; 2507 assert(x.contains(aa_[1 .. 2])); 2508 assert(x.containsUsingLinearSearch(aa_[1 .. 2])); 2509 assert(aa_[1 .. 2] in x); 2510 2511 char[2] aa__; aa__ = "aa"; 2512 assert(x.contains(aa__[1 .. 2])); 2513 assert(x.containsUsingLinearSearch(aa__[1 .. 2])); 2514 assert(aa__[1 .. 2] in x); 2515 2516 const bb = "bb"; 2517 2518 assert(x.insertAndReturnElement(bb[0 .. 1]) is bb[0 .. 1]); // returns newly added ref 2519 assert(x.insertAndReturnElement(bb[0 .. 1]) !is "b"); // return other ref not equal new literal 2520 x.insert(bb[0 .. 1]); 2521 assert(x.contains(bb[1 .. 2])); 2522 assert(x.containsUsingLinearSearch(bb[1 .. 2])); 2523 2524 x.remove(aa[0 .. 1]); 2525 assert(!x.contains(aa[1 .. 2])); 2526 assert(!x.containsUsingLinearSearch(aa[1 .. 2])); 2527 assert(x.contains(bb[1 .. 2])); 2528 assert(x.containsUsingLinearSearch(bb[1 .. 2])); 2529 2530 x.remove(bb[0 .. 1]); 2531 assert(!x.contains(bb[1 .. 2])); 2532 assert(!x.containsUsingLinearSearch(bb[1 .. 2])); 2533 2534 x.insert("a"); 2535 x.insert("b"); 2536 assert(x.contains("a")); 2537 assert(x.containsUsingLinearSearch("a")); 2538 assert(x.contains("b")); 2539 assert(x.containsUsingLinearSearch("b")); 2540 2541 debug static assert(!__traits(compiles, { testEscapeShouldFail(); } )); 2542 // TODO this should fail: 2543 // TODO debug static assert(!__traits(compiles, { testEscapeShouldFailFront(); } )); 2544 } 2545 2546 /// `string` as key 2547 @safe pure nothrow unittest 2548 { 2549 version(showEntries) dbg(); 2550 import nxt.digestx.fnv : FNV; 2551 alias X = OpenHashSet!(string, FNV!(64, true)); 2552 auto x = X(); 2553 2554 char[2] cc = "cc"; // mutable chars 2555 assert(x.insertAndReturnElement(cc[]) !is cc[]); // will allocate new slice 2556 2557 const cc_ = "cc"; // immutable chars 2558 assert(x.insertAndReturnElement(cc_[]) !is cc[]); // will not allocate 2559 } 2560 2561 /// array container as value type 2562 @safe pure nothrow @nogc unittest 2563 { 2564 import std.meta : AliasSeq; 2565 import std.typecons : Nullable; 2566 import nxt.container_traits : mustAddGCRange; 2567 import nxt.digestx.fnv : FNV; 2568 import nxt.array_help : s; 2569 version(showEntries) dbg(); 2570 2571 import nxt.dynamic_array : Array = DynamicArray; 2572 2573 alias K = Nullable!(uint, uint.max); 2574 2575 alias VE = Nullable!(uint, uint.max); 2576 alias V = OpenHashSet!(VE, FNV!(64, true)); 2577 2578 debug static assert(!mustAddGCRange!V); 2579 2580 foreach (X; AliasSeq!(OpenHashMap!(K, V, FNV!(64, true)))) 2581 { 2582 const VE n = 600; 2583 2584 auto x = X(); 2585 2586 { // scoped range 2587 auto xkeys = x.byKey; 2588 assert(xkeys.length == 0); 2589 foreach (ref key; xkeys) 2590 { 2591 debug static assert(is(typeof(key) == const(K))); 2592 assert(0); 2593 } 2594 foreach (ref key; X().byKey) 2595 { 2596 debug static assert(is(typeof(key) == const(K))); 2597 assert(0); 2598 } 2599 } 2600 2601 foreach (immutable i; 0 .. n) 2602 { 2603 assert(x.length == i); 2604 2605 auto key = K(i); 2606 auto value = V.withElements([VE(i)].s); 2607 2608 x[key] = value.dup; 2609 assert(x.length == i + 1); 2610 assert(x.contains(key)); 2611 // TODO assert(x.containsUsingLinearSearch(key)); 2612 { 2613 auto valuePtr = key in x; 2614 assert(valuePtr && *valuePtr == value); 2615 } 2616 2617 x.remove(key); 2618 assert(x.length == i); 2619 assert(!x.contains(key)); 2620 assert(key !in x); 2621 2622 x[key] = value.dup; 2623 assert(x.length == i + 1); 2624 assert(x.contains(key)); 2625 { 2626 auto valuePtr = key in x; 2627 assert(valuePtr && *valuePtr == value); 2628 } 2629 } 2630 2631 assert(x is x); 2632 2633 x = x.dup; 2634 2635 auto y = x.dup; 2636 assert(x !is y); 2637 assert(x.length == y.length); 2638 2639 assert(y == x); 2640 assert(x == y); 2641 2642 foreach (ref key; x.byKey) 2643 { 2644 assert(x.contains(key)); 2645 } 2646 2647 foreach (ref keyValue; x.byKeyValue) 2648 { 2649 assert(x.contains(keyValue.key)); 2650 auto keyValuePtr = keyValue.key in x; 2651 assert(keyValuePtr && 2652 *keyValuePtr == keyValue.value); 2653 } 2654 2655 foreach (immutable i; 0 .. n) 2656 { 2657 assert(x.length == n - i); 2658 2659 auto key = K(i); 2660 auto value = V.withElements([VE(i)].s); 2661 2662 assert(x.contains(key)); 2663 { 2664 auto valuePtr = key in x; 2665 assert(valuePtr && *valuePtr == value); 2666 } 2667 2668 x.remove(key); 2669 assert(!x.contains(key)); 2670 assert(key !in x); 2671 } 2672 2673 auto z = y.dup; 2674 assert(y == z); 2675 2676 /* remove all elements in `y` using `removeAllMatching` and all elements 2677 * in `z` using `removeAllMatching` */ 2678 foreach (immutable i; 0 .. n) 2679 { 2680 assert(y.length == n - i); 2681 assert(z.length == n - i); 2682 2683 auto key = K(i); 2684 auto value = V.withElements([VE(i)].s); 2685 2686 assert(y.contains(key)); 2687 { 2688 auto valuePtr = key in y; 2689 assert(valuePtr && *valuePtr == value); 2690 } 2691 assert(z.contains(key)); 2692 { 2693 auto valuePtr = key in z; 2694 assert(valuePtr && *valuePtr == value); 2695 } 2696 2697 y.remove(key); 2698 assert(z.removeAllMatching!((const scope ref element) => element.key is key) == 1); 2699 assert(y == z); 2700 2701 assert(!y.contains(key)); 2702 assert(!z.contains(key)); 2703 2704 assert(key !in y); 2705 assert(key !in z); 2706 } 2707 } 2708 } 2709 2710 /// r-value and l-value intersection 2711 @safe pure nothrow @nogc unittest 2712 { 2713 import core.lifetime : move; 2714 import std.typecons : Nullable; 2715 import nxt.digestx.fnv : FNV; 2716 import nxt.array_help : s; 2717 2718 version(showEntries) dbg(); 2719 alias K = Nullable!(uint, uint.max); 2720 alias X = OpenHashSet!(K, FNV!(64, true)); 2721 2722 auto x = X(); 2723 2724 { // scoped range 2725 foreach (ref xe; x.byElement) { assert(0); } 2726 } 2727 2728 auto x0 = X.init; 2729 assert(x0.length == 0); 2730 assert(x0._store.length == 0); 2731 assert(!x0.contains(K(1))); 2732 2733 auto x1 = X.withElements([K(12)].s); 2734 assert(x1.length == 1); 2735 assert(x1.contains(K(12))); 2736 2737 auto x2 = X.withElements([K(10), K(12)].s); 2738 assert(x2.length == 2); 2739 assert(x2.contains(K(10))); 2740 assert(x2.contains(K(12))); 2741 2742 auto x3 = X.withElements([K(12), K(13), K(14)].s); 2743 assert(x3.length == 3); 2744 assert(x3.contains(K(12))); 2745 assert(x3.contains(K(13))); 2746 assert(x3.contains(K(14))); 2747 2748 auto z = X.withElements([K(10), K(12), K(13), K(15)].s); 2749 assert(z.length == 4); 2750 assert(z.contains(K(10))); 2751 assert(z.contains(K(12))); 2752 assert(z.contains(K(13))); 2753 assert(z.contains(K(15))); 2754 2755 auto y = move(z).intersectedWith(x2); 2756 assert(y.length == 2); 2757 assert(y.contains(K(10))); 2758 assert(y.contains(K(12))); 2759 assert(y.containsUsingLinearSearch(K(10))); 2760 assert(y.containsUsingLinearSearch(K(12))); 2761 } 2762 2763 /// r-value and r-value intersection 2764 @safe pure nothrow @nogc unittest 2765 { 2766 version(showEntries) dbg(); 2767 2768 import std.typecons : Nullable; 2769 import nxt.digestx.fnv : FNV; 2770 import nxt.array_help : s; 2771 2772 alias K = Nullable!(uint, uint.max); 2773 alias X = OpenHashSet!(K, FNV!(64, true)); 2774 2775 auto y = X.withElements([K(10), K(12), K(13), K(15)].s).intersectedWith(X.withElements([K(12), K(13)].s)); 2776 assert(y.length == 2); 2777 assert(y.contains(K(12))); 2778 assert(y.contains(K(13))); 2779 assert(y.containsUsingLinearSearch(K(12))); 2780 assert(y.containsUsingLinearSearch(K(13))); 2781 } 2782 2783 /** Returns: `x` eagerly intersected with `y`. 2784 TODO move to container_algorithm.d. 2785 */ 2786 auto intersectWith(C1, C2)(ref C1 x, 2787 auto ref const(C2) y) 2788 if (isInstanceOf!(OpenHashMap, C1) && 2789 isInstanceOf!(OpenHashMap, C2)) 2790 { 2791 return x.removeAllMatching!(_ => !y.contains(_)); 2792 } 2793 2794 /// r-value and l-value intersection 2795 @safe pure nothrow @nogc unittest 2796 { 2797 version(showEntries) dbg(); 2798 2799 import std.typecons : Nullable; 2800 import nxt.digestx.fnv : FNV; 2801 import nxt.array_help : s; 2802 2803 alias K = Nullable!(uint, uint.max); 2804 alias X = OpenHashSet!(K, FNV!(64, true)); 2805 2806 auto x = X.withElements([K(12), K(13)].s); 2807 auto y = X.withElements([K(10), K(12), K(13), K(15)].s); 2808 y.intersectWith(x); 2809 assert(y.length == 2); 2810 assert(y.contains(K(12))); 2811 assert(y.containsUsingLinearSearch(K(12))); 2812 assert(y.contains(K(13))); 2813 assert(y.containsUsingLinearSearch(K(13))); 2814 } 2815 2816 /// Range over elements of l-value instance of this. 2817 static struct ByLvalueElement(SomeMap) // public for now because this is needed in `knet.zing.Zing.EdgesOfRels` 2818 { 2819 pragma(inline, true): 2820 // TODO functionize 2821 import std.traits : isMutable; 2822 static if (isAddress!(SomeMap.ElementType)) // for reference types 2823 { 2824 /// Get reference to front element. 2825 @property scope SomeMap.ElementType front()() return @trusted 2826 { 2827 // cast to head-const for class key 2828 return (cast(typeof(return))_table._store[_binIndex]); 2829 } 2830 } 2831 else 2832 { 2833 /// Get reference to front element. 2834 @property scope auto ref front() return @trusted 2835 { 2836 return *(cast(const(SomeMap.ElementType)*)&_table._store[_binIndex]); // propagate constnes 2837 } 2838 } 2839 import core.internal.traits : Unqual; 2840 // unqual to reduce number of instantations of `LvalueElementRef` 2841 public LvalueElementRef!(Unqual!SomeMap) _elementRef; 2842 alias _elementRef this; 2843 } 2844 2845 /// Range over elements of r-value instance of this. 2846 static private struct ByRvalueElement(SomeMap) 2847 { 2848 @disable this(this); 2849 pragma(inline, true): 2850 static if (isAddress!(SomeMap.ElementType)) // for reference types 2851 { 2852 /// Get reference to front element. 2853 @property scope SomeMap.ElementType front()() return @trusted 2854 { 2855 // cast to head-const for class key 2856 return cast(typeof(return))_table._store[_binIndex]; 2857 } 2858 } 2859 else 2860 { 2861 /// Get reference to front element. 2862 @property scope auto ref front() return 2863 { 2864 return *(cast(const(SomeMap.ElementType)*)&_table._store[_binIndex]); // propagate constnes 2865 } 2866 } 2867 import core.internal.traits : Unqual; 2868 public RvalueElementRef!(Unqual!SomeMap) _elementRef; 2869 alias _elementRef this; 2870 } 2871 2872 /** Returns: range that iterates through the elements of `c` in undefined order. 2873 */ 2874 auto byElement(SomeMap)(auto ref return SomeMap c) @trusted 2875 if (isInstanceOf!(OpenHashMap, SomeMap) && 2876 !SomeMap.hasValue) 2877 { 2878 import core.internal.traits : Unqual; 2879 alias M = Unqual!SomeMap; 2880 alias C = const(SomeMap); // be const for now 2881 static if (__traits(isRef, c)) // `c` is an l-value and must be borrowed 2882 { 2883 auto result = ByLvalueElement!C((LvalueElementRef!(M)(cast(M*)&c))); 2884 } 2885 else // `c` was is an r-value and can be moved 2886 { 2887 import core.lifetime : move; 2888 auto result = ByRvalueElement!C((RvalueElementRef!(M)(move(*(cast(M*)&c))))); // reinterpret 2889 } 2890 result.findNextNonEmptyBin(); 2891 return result; 2892 } 2893 alias range = byElement; // EMSI-container naming 2894 2895 static private struct ByKey_lvalue(SomeMap) 2896 if (isInstanceOf!(OpenHashMap, SomeMap) && 2897 SomeMap.hasValue) 2898 { 2899 @property const scope auto ref front() return // key access must be const, TODO auto ref => ref K 2900 { 2901 pragma(inline, true); 2902 return _table._store[_binIndex].key; 2903 } 2904 import core.internal.traits : Unqual; 2905 public LvalueElementRef!(Unqual!SomeMap) _elementRef; 2906 alias _elementRef this; 2907 } 2908 2909 static private struct ByKey_rvalue(SomeMap) 2910 if (isInstanceOf!(OpenHashMap, SomeMap) && 2911 SomeMap.hasValue) 2912 { 2913 @property const scope auto ref front() return // key access must be const, TODO auto ref => ref K 2914 { 2915 pragma(inline, true); 2916 return _table._store[_binIndex].key; 2917 } 2918 import core.internal.traits : Unqual; 2919 public RvalueElementRef!(Unqual!SomeMap) _elementRef; 2920 alias _elementRef this; 2921 } 2922 2923 /** Returns: range that iterates through the keys of `c` in undefined order. 2924 */ 2925 auto byKey(SomeMap)(auto ref /*TODO return*/ SomeMap c) @trusted 2926 if (isInstanceOf!(OpenHashMap, SomeMap) && 2927 SomeMap.hasValue) 2928 { 2929 import core.internal.traits : Unqual; 2930 alias M = Unqual!SomeMap; 2931 alias C = const(SomeMap); // be const 2932 static if (__traits(isRef, c)) // `c` is an l-value and must be borrowed 2933 { 2934 auto result = ByKey_lvalue!C((LvalueElementRef!(M)(cast(M*)&c))); 2935 } 2936 else // `c` was is an r-value and can be moved 2937 { 2938 import core.lifetime : move; 2939 auto result = ByKey_rvalue!C((RvalueElementRef!M(move(*(cast(M*)&c))))); // reinterpret 2940 } 2941 result.findNextNonEmptyBin(); 2942 return result; 2943 } 2944 2945 static private struct ByValue_lvalue(SomeMap) 2946 if (isInstanceOf!(OpenHashMap, SomeMap) && 2947 SomeMap.hasValue) 2948 { 2949 @property scope auto ref front() return @trusted // TODO auto ref => ref V 2950 { 2951 pragma(inline, true); 2952 // TODO functionize 2953 import std.traits : isMutable; 2954 static if (isMutable!(SomeMap)) // TODO can this be solved without this `static if`? 2955 { 2956 alias E = SomeMap.ValueType; 2957 } 2958 else 2959 { 2960 alias E = const(SomeMap.ValueType); 2961 } 2962 return *(cast(E*)&_table._store[_binIndex].value); 2963 } 2964 import core.internal.traits : Unqual; 2965 public LvalueElementRef!(Unqual!SomeMap) _elementRef; 2966 alias _elementRef this; 2967 } 2968 2969 static private struct ByValue_rvalue(SomeMap) 2970 if (isInstanceOf!(OpenHashMap, SomeMap) && 2971 SomeMap.hasValue) 2972 { 2973 @property scope auto ref front() return @trusted // TODO auto ref => ref V 2974 { 2975 pragma(inline, true); 2976 // TODO functionize 2977 import std.traits : isMutable; 2978 static if (isMutable!(SomeMap)) // TODO can this be solved without this `static if`? 2979 { 2980 alias E = SomeMap.ValueType; 2981 } 2982 else 2983 { 2984 alias E = const(SomeMap.ValueType); 2985 } 2986 return *(cast(E*)&_table._store[_binIndex].value); 2987 } 2988 import core.internal.traits : Unqual; 2989 public RvalueElementRef!(Unqual!SomeMap) _elementRef; 2990 alias _elementRef this; 2991 } 2992 2993 /** Returns: range that iterates through the values of `c` in undefined order. 2994 */ 2995 auto byValue(SomeMap)(auto ref return SomeMap c) @trusted 2996 if (isInstanceOf!(OpenHashMap, SomeMap) && 2997 SomeMap.hasValue) 2998 { 2999 import core.internal.traits : Unqual; 3000 import std.traits : isMutable; 3001 alias M = Unqual!SomeMap; 3002 alias C = const(SomeMap); 3003 static if (__traits(isRef, c)) // `c` is an l-value and must be borrowed 3004 { 3005 auto result = ByValue_lvalue!SomeMap((LvalueElementRef!(M)(cast(M*)&c))); 3006 } 3007 else // `c` was is an r-value and can be moved 3008 { 3009 import core.lifetime : move; 3010 auto result = ByValue_rvalue!C((RvalueElementRef!M(move(*(cast(M*)&c))))); // reinterpret 3011 } 3012 result.findNextNonEmptyBin(); 3013 return result; 3014 } 3015 3016 static private struct ByKeyValue_lvalue(SomeMap) 3017 if (isInstanceOf!(OpenHashMap, SomeMap) && 3018 SomeMap.hasValue) 3019 { 3020 @property scope auto ref front() return @trusted // TODO auto ref => ref T 3021 { 3022 pragma(inline, true); 3023 // TODO functionize 3024 import std.traits : isMutable; 3025 static if (isMutable!(SomeMap)) 3026 { 3027 alias E = SomeMap.KeyValueType; 3028 } 3029 else 3030 { 3031 alias E = const(SomeMap.T); 3032 } 3033 return *(cast(E*)&_table._store[_binIndex]); 3034 } 3035 import core.internal.traits : Unqual; 3036 public LvalueElementRef!(Unqual!SomeMap) _elementRef; 3037 alias _elementRef this; 3038 } 3039 3040 /** Returns: range that iterates through the key-value-pairs of `c` in undefined order. 3041 */ 3042 auto byKeyValue(SomeMap)(auto ref return SomeMap c) @trusted 3043 if (isInstanceOf!(OpenHashMap, SomeMap) && 3044 SomeMap.hasValue) 3045 { 3046 import core.internal.traits : Unqual; 3047 alias M = Unqual!SomeMap; 3048 static if (__traits(isRef, c)) // `c` is an l-value and must be borrowed 3049 { 3050 auto result = ByKeyValue_lvalue!SomeMap((LvalueElementRef!(M)(cast(M*)&c))); 3051 } 3052 else // `c` was is an r-value and can be moved 3053 { 3054 import core.lifetime : move; 3055 auto result = ByKeyValue_rvalue!SomeMap((RvalueElementRef!M(move(*(cast(M*)&c))))); // reinterpret 3056 } 3057 result.findNextNonEmptyBin(); 3058 return result; 3059 } 3060 3061 /// make range from l-value and r-value. element access is always const 3062 @safe pure unittest 3063 { 3064 version(showEntries) dbg(); 3065 3066 import core.exception : RangeError, AssertError; 3067 import std.typecons : Nullable; 3068 import nxt.digestx.fnv : FNV; 3069 import nxt.array_help : s; 3070 debug import std.exception : assertThrown, assertNotThrown; 3071 3072 import std.algorithm.searching : count; 3073 alias K = Nullable!(uint, uint.max); 3074 alias X = OpenHashSet!(K, 3075 FNV!(64, true), 3076 defaultKeyEqualPredOf!K, 3077 Mallocator.instance, 3078 true); 3079 3080 auto k11 = K(11); 3081 auto k22 = K(22); 3082 auto k33 = K(33); 3083 auto ks = [k11, k22, k33].s; 3084 auto k44 = K(44); 3085 3086 // mutable 3087 auto x = X.withElements(ks); 3088 assert(!x.contains(k44)); 3089 assert(!x.containsUsingLinearSearch(k44)); 3090 assert(x.length == 3); 3091 3092 assert(x.byElement.count == x.length); 3093 foreach (e; x.byElement) // from l-value 3094 { 3095 debug static assert(is(typeof(e) == const(K))); // always const access 3096 3097 // range invalidation forbidden: 3098 debug 3099 { 3100 assertThrown!AssertError(x.reserveExtra(1)); // range invalidation 3101 assertThrown!AssertError(x.clear()); // range invalidation 3102 assertThrown!AssertError(x.insert(k11)); // range invalidation 3103 assertThrown!AssertError(x.insertN([k11].s)); // range invalidation 3104 assertThrown!AssertError(x.remove(k11)); // range invalidation 3105 } 3106 3107 // allowed 3108 assert(x.contains(e)); 3109 assert(x.containsUsingLinearSearch(e)); 3110 3111 const eHit = e in x; 3112 assert(eHit); // found 3113 assert(*eHit is e); // and the value equals what we searched for 3114 3115 const eDup = x.dup; // duplication is `const` and allowed 3116 } 3117 3118 // const 3119 const y = X.withElements(ks); 3120 assert(!x.contains(k44)); 3121 assert(!x.containsUsingLinearSearch(k44)); 3122 foreach (e; y.byElement) // from l-value 3123 { 3124 auto z = y.byElement; // ok to read-borrow again 3125 assert(y.contains(e)); 3126 assert(y.containsUsingLinearSearch(e)); 3127 debug static assert(is(typeof(e) == const(K))); 3128 } 3129 3130 foreach (e; X.withElements([K(11)].s).byElement) // from r-value 3131 { 3132 assert(e == K(11)); 3133 debug static assert(is(typeof(e) == const(K))); // always const access 3134 } 3135 } 3136 3137 /// range checking 3138 @safe pure unittest 3139 { 3140 version(showEntries) dbg(); 3141 import core.exception : RangeError, AssertError; 3142 import std.typecons : Nullable; 3143 import nxt.digestx.fnv : FNV; 3144 debug import std.exception : assertThrown, assertNotThrown; 3145 immutable n = 11; 3146 3147 alias K = Nullable!(uint, uint.max); 3148 alias V = uint; 3149 3150 alias X = OpenHashMap!(K, V, FNV!(64, true)); 3151 3152 auto s = X.withCapacity(n); 3153 3154 void dummy(ref V value) {} 3155 3156 debug assertThrown!RangeError(dummy(s[K(0)])); 3157 3158 foreach (immutable i; 0 .. n) 3159 { 3160 const k = K(i); 3161 s[k] = V(i); 3162 debug assertNotThrown!RangeError(dummy(s[k])); 3163 } 3164 3165 foreach (immutable i; 0 .. n) 3166 { 3167 const k = K(i); 3168 assert(s.remove(k)); 3169 debug assertThrown!RangeError(dummy(s[k])); 3170 } 3171 3172 s[K(0)] = V.init; 3173 auto vp = K(0) in s; 3174 debug static assert(is(typeof(vp) == V*)); 3175 assert((*vp) == V.init); 3176 3177 assert(s.remove(K(0))); 3178 assert(K(0) !in s); 3179 3180 X t; 3181 t.reserveExtra(4096); 3182 3183 t.clear(); 3184 } 3185 3186 /// class as value 3187 @safe pure unittest 3188 { 3189 version(showEntries) dbg(); 3190 import core.exception : RangeError, AssertError; 3191 import std.typecons : Nullable; 3192 debug import std.exception : assertThrown, assertNotThrown; 3193 import nxt.digestx.fnv : FNV; 3194 3195 immutable n = 11; 3196 3197 alias K = Nullable!(uint, uint.max); 3198 class V 3199 { 3200 this(uint data) { this.data = data; } 3201 uint data; 3202 } 3203 3204 alias X = OpenHashMap!(K, V, FNV!(64, true)); 3205 3206 auto s = X.withCapacity(n); 3207 3208 void dummy(ref V value) {} 3209 3210 debug assertThrown!RangeError(dummy(s[K(0)])); 3211 3212 foreach (immutable i; 0 .. n) 3213 { 3214 const k = K(i); 3215 s[k] = new V(i); 3216 debug assertNotThrown!RangeError(dummy(s[k])); 3217 } 3218 3219 // test range 3220 { 3221 auto sr = s.byKeyValue; // scoped range 3222 assert(sr.length == n); 3223 foreach (immutable i; 0 .. n) 3224 { 3225 sr.popFront(); 3226 assert(sr.length == n - i - 1); 3227 } 3228 } 3229 3230 foreach (immutable i; 0 .. n) 3231 { 3232 const k = K(i); 3233 assert(s.remove(k)); 3234 debug assertThrown!RangeError(dummy(s[k])); 3235 } 3236 3237 s[K(0)] = V.init; 3238 auto vp = K(0) in s; 3239 debug static assert(is(typeof(vp) == V*)); 3240 3241 assert(s.remove(K(0))); 3242 assert(K(0) !in s); 3243 3244 X t; 3245 t.reserveExtra(4096); 3246 } 3247 3248 /// constness inference of ranges 3249 pure nothrow unittest 3250 { 3251 version(showEntries) dbg(); 3252 import std.typecons : Nullable; 3253 import nxt.digestx.fnv : FNV; 3254 3255 alias K = Nullable!(uint, uint.max); 3256 class V 3257 { 3258 this(uint data) { this.data = data; } 3259 uint data; 3260 } 3261 3262 alias X = OpenHashMap!(K, V, FNV!(64, true)); 3263 const x = X(); 3264 3265 foreach (ref e; x.byKey) 3266 { 3267 debug static assert(is(typeof(e) == const(X.KeyType))); 3268 } 3269 3270 foreach (ref e; x.byValue) 3271 { 3272 debug static assert(is(typeof(e) == const(X.ValueType))); 3273 } 3274 3275 foreach (e; x.byKeyValue) 3276 { 3277 debug static assert(is(typeof(e.key) == const(X.KeyType))); 3278 debug static assert(is(typeof(e.value) == const(X.ValueType))); 3279 debug static assert(is(typeof(e) == const(X.ElementType))); 3280 } 3281 } 3282 3283 /// range key constness and value mutability with `class` value 3284 pure nothrow unittest 3285 { 3286 version(showEntries) dbg(); 3287 import std.typecons : Nullable; 3288 import nxt.digestx.fnv : FNV; 3289 3290 struct S 3291 { 3292 uint value; 3293 } 3294 alias K = Nullable!(S, S(uint.min)); // use uint.min to trigger use of faster `Allocator.allocateZeroed` 3295 3296 class V 3297 { 3298 this(uint data) { this.data = data; } 3299 uint data; 3300 } 3301 3302 alias X = OpenHashMap!(K, V, FNV!(64, true)); 3303 auto x = X(); 3304 3305 x[K(S(42))] = new V(43); 3306 3307 assert(x.length == 1); 3308 3309 foreach (e; x.byValue) // `e` is auto ref 3310 { 3311 debug static assert(is(typeof(e) == X.ValueType)); // mutable access to value 3312 assert(e.data == 43); 3313 3314 // value mutation side effects 3315 e.data += 1; 3316 assert(e.data == 44); 3317 e.data -= 1; 3318 assert(e.data == 43); 3319 } 3320 3321 foreach (ref e; x.byKeyValue) // `e` is auto ref 3322 { 3323 debug static assert(is(typeof(e.key) == const(X.KeyType))); // const access to key 3324 debug static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value 3325 3326 assert(e.key.value == 42); 3327 assert(e.value.data == 43); 3328 3329 // key cannot be mutated 3330 debug static assert(!__traits(compiles, { e.key.value += 1; })); 3331 3332 // value mutation side effects 3333 e.value.data += 1; 3334 assert(e.value.data == 44); 3335 e.value.data -= 1; 3336 assert(e.value.data == 43); 3337 } 3338 } 3339 3340 /// range key constness and value mutability with `class` key and `class` value 3341 pure nothrow unittest 3342 { 3343 import nxt.digestx.fnv : FNV; 3344 3345 version(showEntries) dbg(); 3346 class K 3347 { 3348 this(uint value) 3349 { 3350 this.value = value; 3351 } 3352 3353 @property bool opEquals(const scope typeof(this) rhs) const 3354 { 3355 return value == rhs.value; 3356 } 3357 3358 uint value; 3359 } 3360 3361 class V 3362 { 3363 this(uint data) { this.data = data; } 3364 uint data; 3365 } 3366 3367 alias X = OpenHashMap!(K, V, FNV!(64, true)); 3368 auto x = X(); 3369 3370 x[new K(42)] = new V(43); 3371 3372 assert(x.length == 1); 3373 3374 foreach (e; x.byValue) // `e` is auto ref 3375 { 3376 debug static assert(is(typeof(e) == X.ValueType)); // mutable access to value 3377 assert(e.data == 43); 3378 3379 // value mutation side effects 3380 e.data += 1; 3381 assert(e.data == 44); 3382 e.data -= 1; 3383 assert(e.data == 43); 3384 } 3385 3386 foreach (ref e; x.byKeyValue) // `e` is auto ref 3387 { 3388 debug static assert(is(typeof(e.key) == X.KeyType)); // mutable access to class key 3389 debug static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value 3390 3391 assert(e.key.value == 42); 3392 assert(e.value.data == 43); 3393 3394 // class key itself should not be mutable 3395 debug static assert(!__traits(compiles, { e.key = null; })); 3396 3397 // members of key can be mutated 3398 debug static assert(__traits(compiles, { e.key.value += 1; })); 3399 3400 // value mutation side effects 3401 e.value.data += 1; 3402 assert(e.value.data == 44); 3403 e.value.data -= 1; 3404 assert(e.value.data == 43); 3405 } 3406 } 3407 3408 /// range key constness and value mutability with `class` key and `class` value 3409 pure nothrow unittest 3410 { 3411 import nxt.digestx.fnv : FNV; 3412 version(showEntries) dbg(); 3413 class K 3414 { 3415 this(uint value) 3416 { 3417 this.value = value; 3418 } 3419 uint value; 3420 } 3421 3422 struct V 3423 { 3424 this(uint data) { this.data = data; } 3425 @disable this(this); 3426 uint data; 3427 } 3428 3429 alias X = OpenHashMap!(K, V, FNV!(64, true)); 3430 auto x = X(); 3431 3432 scope key42 = new K(42); 3433 x[key42] = V(43); 3434 3435 assert(x.length == 1); 3436 3437 foreach (ref e; x.byValue) // `e` is auto ref 3438 { 3439 debug static assert(is(typeof(e) == X.ValueType)); // mutable access to value 3440 assert(e.data == 43); 3441 3442 // value mutation side effects 3443 e.data += 1; 3444 assert(e.data == 44); 3445 e.data -= 1; 3446 assert(e.data == 43); 3447 } 3448 3449 foreach (ref e; x.byKeyValue) // `e` is auto ref 3450 { 3451 debug static assert(is(typeof(e.key) == X.KeyType)); // mutable access to class key 3452 debug static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value 3453 3454 assert(e.key.value == 42); 3455 assert(e.value.data == 43); 3456 3457 // value mutation side effects 3458 e.value.data += 1; 3459 assert(e.value.data == 44); 3460 e.value.data -= 1; 3461 assert(e.value.data == 43); 3462 } 3463 3464 assert(x.length == 1); 3465 3466 assert(x.remove(key42)); 3467 assert(x.length == 0); 3468 3469 x[key42] = V(43); 3470 assert(x.length == 1); 3471 } 3472 3473 version(unittest) 3474 { 3475 T make(T)(ulong value) 3476 { 3477 static if (is(T == class)) 3478 { 3479 return new T(value); 3480 } 3481 else 3482 { 3483 return T(value); 3484 } 3485 } 3486 } 3487 3488 /// test various things 3489 @trusted unittest 3490 { 3491 import std.meta : AliasSeq; 3492 import std.typecons : Nullable; 3493 import std.algorithm.comparison : equal; 3494 import nxt.container_traits : mustAddGCRange; 3495 import nxt.digestx.fnv : FNV; 3496 import nxt.array_help : s; 3497 3498 version(showEntries) dbg(); 3499 const n = 100; 3500 3501 void testEmptyAll(K, V, X)(ref X x, size_t n, 3502 scope K[] keys) 3503 { 3504 assert(x.length == n); 3505 foreach (key; keys) 3506 { 3507 static if (X.hasValue) 3508 { 3509 const element = X.ElementType(key, V.init); 3510 } 3511 else 3512 { 3513 alias element = key; 3514 } 3515 3516 assert(x.length == n - key.get); 3517 3518 const hitPtr = key in x; 3519 static if (X.hasValue) 3520 { 3521 assert(hitPtr && *hitPtr is element.value); 3522 } 3523 else 3524 { 3525 assert(hitPtr && *hitPtr is element); 3526 } 3527 3528 assert(x.remove(key)); 3529 assert(x.length == n - key.get - 1); 3530 3531 static if (!X.hasValue) 3532 { 3533 assert(!x.contains(key)); 3534 assert(!x.containsUsingLinearSearch(key)); 3535 } 3536 assert(key !in x); 3537 assert(!x.remove(key)); 3538 assert(x.length == n - key.get - 1); 3539 } 3540 3541 assert(x.length == 0); 3542 3543 x.clear(); 3544 assert(x.length == 0); 3545 } 3546 3547 X testDup(X)(scope ref X x, size_t n) 3548 { 3549 typeof(return) y = x.dup; 3550 3551 assert(x._store.ptr !is y._store.ptr); 3552 assert(x.length == y.length); 3553 assert(y.length == n); 3554 // non-symmetric algorithm so both are needed 3555 assert(y == x); 3556 assert(x == y); 3557 3558 static if (X.hasValue) 3559 { 3560 assert(equal(x.byKey, 3561 y.byKey)); 3562 assert(equal(x.byValue, 3563 y.byValue)); 3564 assert(equal(x.byKeyValue, 3565 y.byKeyValue)); 3566 } 3567 else 3568 { 3569 assert(equal(x.byElement, 3570 y.byElement)); 3571 } 3572 3573 debug static assert(!__traits(compiles, { const _ = x < y; })); // no ordering 3574 3575 return y; 3576 } 3577 3578 alias NullableUlong = Nullable!(ulong, ulong.max); 3579 3580 static class SomeSimpleClass 3581 { 3582 @safe pure nothrow @nogc 3583 this(ulong value) 3584 { 3585 this._value = value; 3586 } 3587 3588 @safe pure nothrow @nogc 3589 ulong get() const 3590 { 3591 return _value; 3592 } 3593 3594 @property void toString(scope void delegate(scope const(char)[]) sink) const 3595 { 3596 import std.format : formattedWrite; 3597 sink.formattedWrite(typeof(this).stringof, "(%s)", _value); 3598 } 3599 3600 @property bool opEquals(const scope typeof(this) rhs) const 3601 { 3602 return _value == rhs._value; 3603 } 3604 3605 private ulong _value; 3606 } 3607 3608 debug static assert(mustAddGCRange!string); 3609 3610 foreach (K; AliasSeq!(SomeSimpleClass, 3611 NullableUlong)) 3612 { 3613 foreach (V; AliasSeq!(string, 3614 int, 3615 void)) 3616 { 3617 alias X = OpenHashMap!(K, V, FNV!(64, true)); 3618 3619 auto k11 = make!K(11); 3620 auto k12 = make!K(12); 3621 auto k13 = make!K(13); 3622 3623 static if (!X.hasValue) 3624 { 3625 auto x = X.withElements([k11, k12, k13].s); 3626 3627 import std.algorithm : count; 3628 3629 // ByLvalueElement 3630 auto xr = x.byElement; 3631 3632 alias R = typeof(xr); 3633 import std.range.primitives : isInputRange; 3634 import std.traits : ReturnType; 3635 debug static assert(is(typeof(R.init) == R)); 3636 debug static assert(is(ReturnType!((R xr) => xr.empty) == bool)); 3637 3638 debug static assert(!__traits(compiles, { xr.front == K.init; })); // always head-const 3639 auto f = xr.front; 3640 static if (is(K == class)) 3641 { 3642 debug static assert(is(typeof(f) == K)); // tail-mutable 3643 } 3644 else 3645 { 3646 debug static assert(is(typeof(f) == const(K))); // tail-const 3647 } 3648 3649 debug static assert(is(typeof((R xr) => xr.front))); 3650 debug static assert(!is(ReturnType!((R xr) => xr.front) == void)); 3651 debug static assert(is(typeof((R xr) => xr.popFront))); 3652 3653 debug static assert(isInputRange!(typeof(xr))); 3654 3655 assert(x.byElement.count == 3); 3656 3657 X y; 3658 size_t ix = 0; 3659 foreach (ref e; x.byElement) 3660 { 3661 assert(x.contains(e)); 3662 assert(x.containsUsingLinearSearch(e)); 3663 assert(!y.contains(e)); 3664 assert(!y.containsUsingLinearSearch(e)); 3665 static if (is(K == class)) 3666 { 3667 y.insert(cast(K)e); // ugly but ok in tests 3668 } 3669 else 3670 { 3671 y.insert(e); 3672 } 3673 assert(y.contains(e)); 3674 assert(y.containsUsingLinearSearch(e)); 3675 ix++; 3676 } 3677 3678 assert(y.byElement.count == 3); 3679 assert(x == y); 3680 3681 const z = X(); 3682 assert(z.byElement.count == 0); 3683 3684 immutable w = X(); 3685 assert(w.byElement.count == 0); 3686 3687 { 3688 auto xc = X.withElements([k11, k12, k13].s); 3689 assert(xc.length == 3); 3690 assert(xc.contains(k11)); 3691 assert(xc.containsUsingLinearSearch(k11)); 3692 3693 // TODO http://forum.dlang.org/post/kvwrktmameivubnaifdx@forum.dlang.org 3694 xc.removeAllMatching!(_ => _ == k11); 3695 3696 assert(xc.length == 2); 3697 assert(!xc.contains(k11)); 3698 assert(!xc.containsUsingLinearSearch(k11)); 3699 3700 xc.removeAllMatching!(_ => _ == k12); 3701 assert(!xc.contains(k12)); 3702 assert(!xc.containsUsingLinearSearch(k12)); 3703 assert(xc.length == 1); 3704 3705 xc.removeAllMatching!(_ => _ == k13); 3706 assert(!xc.contains(k13)); 3707 assert(!xc.containsUsingLinearSearch(k13)); 3708 assert(xc.length == 0); 3709 3710 // this is ok 3711 foreach (e; xc.byElement) {} 3712 } 3713 3714 { // ByRvalueElement 3715 auto k = X.withElements([k11, k12].s).filtered!(_ => _ != k11).byElement; 3716 debug static assert(isInputRange!(typeof(k))); 3717 assert(k.front == k12); 3718 3719 debug static assert(!__traits(compiles, { k.front = K.init; })); // head-const 3720 static if (is(K == class)) 3721 { 3722 debug static assert(is(typeof(k.front) == K)); // tail-mutable 3723 } 3724 else 3725 { 3726 debug static assert(is(typeof(k.front) == const(K))); // tail-const 3727 } 3728 3729 k.popFront(); 3730 assert(k.empty); 3731 } 3732 3733 { 3734 X q; 3735 auto qv = [make!K(11U), make!K(12U), make!K(13U), make!K(14U)].s; 3736 q.insertN(qv[]); 3737 foreach (e; qv[]) 3738 { 3739 assert(q.contains(e)); 3740 assert(q.containsUsingLinearSearch(e)); 3741 } 3742 q.clear(); 3743 assert(q.empty); 3744 } 3745 } 3746 3747 static if (is(V == string)) 3748 { 3749 debug static assert(mustAddGCRange!V); 3750 debug static assert(mustAddGCRange!(V[1])); 3751 debug static assert(mustAddGCRange!(X.T)); 3752 } 3753 3754 auto x1 = X(); // start empty 3755 3756 // fill x1 3757 3758 import std.array : Appender; 3759 Appender!(K[]) keys; 3760 3761 foreach (immutable key_; 0 .. n) 3762 { 3763 auto key = make!K(key_); 3764 keys.put(key); 3765 3766 // create elements 3767 static if (X.hasValue) 3768 { 3769 auto value = V.init; 3770 auto element = X.ElementType(key, value); 3771 } 3772 else 3773 { 3774 // no assignment because Nullable.opAssign may leave rhs in null state 3775 auto element = key; 3776 } 3777 3778 assert(key !in x1); 3779 3780 assert(x1.length == key.get); 3781 assert(x1.insert(element) == X.InsertionStatus.added); 3782 assert(x1.length == key.get + 1); 3783 3784 static if (X.hasValue) 3785 { 3786 import std.conv : to; 3787 auto e2 = X.ElementType(key, (42 + key_).to!V); 3788 assert(x1.insert(e2) == X.InsertionStatus.modified); 3789 assert(x1.contains(key)); 3790 assert(x1.get(key, V.init) == (42 + key_).to!V); 3791 3792 assert(x1.remove(key)); 3793 assert(!x1.contains(key)); 3794 3795 x1[key] = value; // restore value 3796 assert(x1.contains(key)); 3797 } 3798 3799 assert(x1.length == key.get + 1); 3800 3801 const hitPtr = key in x1; 3802 static if (X.hasValue) 3803 { 3804 assert(hitPtr && *hitPtr == value); 3805 } 3806 else 3807 { 3808 assert(hitPtr && *hitPtr is key); 3809 } 3810 3811 auto status = x1.insert(element); 3812 assert(status == X.InsertionStatus.unmodified); 3813 static if (X.hasValue) 3814 { 3815 assert(x1.insert(key, value) == X.InsertionStatus.unmodified); 3816 } 3817 assert(x1.length == key.get + 1); 3818 3819 assert(key in x1); 3820 } 3821 3822 static if (X.hasValue) 3823 { 3824 import nxt.dynamic_array : Array = DynamicArray; 3825 Array!(X.ElementType) a1; // remember the keys 3826 3827 foreach (const ref key; x1.byKey) 3828 { 3829 auto keyPtr = key in x1; 3830 assert(keyPtr); 3831 a1 ~= X.ElementType(cast(K)key, (*keyPtr)); 3832 } 3833 3834 assert(x1.length == a1.length); 3835 3836 foreach (ae; a1[]) 3837 { 3838 auto keyPtr = ae.key in x1; 3839 assert(keyPtr); 3840 assert((*keyPtr) is ae.value); 3841 } 3842 } 3843 3844 assert(x1.length == n); 3845 3846 auto x2 = testDup(x1, n); 3847 3848 testEmptyAll!(K, V)(x1, n, keys.data); 3849 3850 testEmptyAll!(K, V)(x2, n, keys.data); // should be not affected by emptying of x1 3851 } 3852 } 3853 } 3854 3855 /// 3856 @safe pure nothrow @nogc unittest 3857 { 3858 version(showEntries) dbg(); 3859 import std.typecons : Nullable; 3860 import nxt.digestx.fnv : FNV; 3861 3862 alias X = OpenHashMap!(Nullable!(size_t, size_t.max), size_t, FNV!(64, true)); 3863 import nxt.dynamic_array : Array = DynamicArray; 3864 X x; 3865 // TODO these segfault: 3866 // TODO auto a = Array!(X.KeyType).withElementsOfRange_untested(x.byKey); // l-value byKey 3867 // TODO auto b = Array!(X.KeyType).withElementsOfRange_untested(X().byKey); // r-value byKey 3868 } 3869 3870 /// manual Nullable type 3871 @safe pure unittest 3872 { 3873 import nxt.digestx.fnv : FNV; 3874 3875 static class Zing 3876 { 3877 @safe pure nothrow @nogc: 3878 this(ulong value) { this._value = value; } 3879 private ulong _value; 3880 } 3881 debug static assert(isNullable!Zing); 3882 3883 enum Alt 3884 { 3885 unknown, 3886 a, 3887 b, 3888 c, 3889 d 3890 } 3891 3892 struct ZingRelation 3893 { 3894 Zing zing; 3895 Alt alts; 3896 3897 alias nullifier = zing; 3898 static immutable nullValue = typeof(this).init; 3899 3900 bool opEquals(const scope typeof(this) that) const @safe pure nothrow @nogc 3901 { 3902 return (this.zing is that.zing && 3903 this.alts == that.alts); 3904 } 3905 } 3906 debug static assert(isNullable!ZingRelation); 3907 3908 alias X = OpenHashSet!(ZingRelation, FNV!(64, true)); 3909 debug static assert(X.sizeof == 32); // TODO fix hole handling and change to 24 3910 X x; 3911 3912 scope e = ZingRelation(new Zing(42), Alt.init); 3913 3914 assert(!x.contains(e)); 3915 assert(!x.containsUsingLinearSearch(e)); 3916 assert(x.insert(e) == X.InsertionStatus.added); 3917 assert(x.contains(e)); 3918 assert(x.containsUsingLinearSearch(e)); 3919 } 3920 3921 /// abstract class value type 3922 @safe unittest 3923 { 3924 static abstract class Zing 3925 { 3926 @safe pure nothrow @nogc: 3927 } 3928 static class Node : Zing 3929 { 3930 @safe pure nothrow @nogc: 3931 } 3932 3933 alias X = OpenHashSet!(Zing); 3934 X x; 3935 3936 const Zing cz = new Node(); 3937 x.insert(cz); // ok to insert const 3938 3939 Zing z = new Node(); 3940 x.insert(z); // ok to insert mutable because hashing is on address by default 3941 } 3942 3943 /// class type with default hashing 3944 @safe unittest 3945 { 3946 static class Base 3947 { 3948 static size_t dtorCount = 0; // number of calls to this destructor 3949 @safe nothrow @nogc: 3950 ~this() @nogc { dtorCount += 1; } 3951 pure: 3952 this(ulong value) { this._value = value; } 3953 @property bool opEquals(const scope typeof(this) rhs) const 3954 { 3955 return _value == rhs._value; 3956 } 3957 override hash_t toHash() const 3958 { 3959 return hashOf(_value); 3960 } 3961 private ulong _value; 3962 } 3963 3964 /** Node containing same data members but different type. */ 3965 static class Node : Base 3966 { 3967 @safe pure nothrow @nogc: 3968 this(ulong value) { super(value); } 3969 } 3970 debug static assert(is(Node : Base)); 3971 3972 import nxt.hash_functions : hashOfPolymorphic; // neede to separate hash of `Base(N)` from `Node(N)` 3973 alias X = OpenHashSet!(Base, hashOfPolymorphic, "a && b && (typeid(a) is typeid(b)) && a.opEquals(b)"); 3974 debug static assert(X.sizeof == 24); 3975 X x; 3976 3977 // top-class 3978 scope b42 = new Base(42); 3979 assert(!x.contains(b42)); 3980 assert(!x.containsUsingLinearSearch(b42)); 3981 assert(x.insert(b42) == X.InsertionStatus.added); 3982 assert(x.contains(b42)); 3983 assert(x.containsUsingLinearSearch(b42)); 3984 assert(x.tryGetElementFromCtorParams!Base(42) !is null); 3985 assert(Base.dtorCount == 1); 3986 assert(x.tryGetElementFromCtorParams!Base(42)._value == 42); 3987 assert(Base.dtorCount == 2); 3988 assert(x.tryGetElementFromCtorParams!Base(41) is null); 3989 assert(Base.dtorCount == 3); 3990 3991 // top-class 3992 scope b43 = new Base(43); 3993 assert(!x.contains(b43)); 3994 assert(!x.containsUsingLinearSearch(b43)); 3995 assert(x.insert(b43) == X.InsertionStatus.added); 3996 assert(x.contains(b43)); 3997 assert(x.containsUsingLinearSearch(b43)); 3998 assert(x.tryGetElementFromCtorParams!Base(43) !is null); 3999 assert(Base.dtorCount == 4); 4000 assert(x.tryGetElementFromCtorParams!Base(43)._value == 43); 4001 assert(Base.dtorCount == 5); 4002 4003 // sub-class 4004 assert(x.tryGetElementFromCtorParams!Node(42) is null); 4005 assert(Base.dtorCount == 6); 4006 immutable n42 = new Node(42); 4007 assert(!x.contains(n42)); // mustn't equal to `b42` 4008 assert(!x.containsUsingLinearSearch(n42)); // mustn't equal to `b42` 4009 assert(x.insert(n42) == X.InsertionStatus.added); // added as separate type 4010 assert(x.contains(n42)); 4011 assert(x.containsUsingLinearSearch(n42)); 4012 assert(x.tryGetElementFromCtorParams!Node(42) !is null); 4013 assert(Base.dtorCount == 7); 4014 assert(x.tryGetElementFromCtorParams!Node(42)._value == 42); 4015 assert(Base.dtorCount == 8); 4016 4017 assert(hashOf(b42) == hashOf(n42)); 4018 4019 // sub-class 4020 assert(x.tryGetElementFromCtorParams!Node(43) is null); 4021 assert(Base.dtorCount == 9); 4022 auto n43 = new Node(43); 4023 assert(!x.contains(n43)); // mustn't equal to `b43` 4024 assert(!x.containsUsingLinearSearch(n43)); // mustn't equal to `b43` 4025 assert(x.insert(n43) == X.InsertionStatus.added); // added as separate type 4026 assert(x.contains(n43)); 4027 assert(x.containsUsingLinearSearch(n43)); 4028 assert(x.tryGetElementFromCtorParams!Node(43) !is null); 4029 assert(Base.dtorCount == 10); 4030 assert(x.tryGetElementFromCtorParams!Node(43)._value == 43); 4031 assert(Base.dtorCount == 11); 4032 4033 assert(hashOf(b43) == hashOf(n43)); 4034 } 4035 4036 /// enumeration key 4037 @safe pure unittest 4038 { 4039 import nxt.digestx.fnv : FNV; 4040 4041 enum Alt 4042 { 4043 nullValue, // trait 4044 a, b, c, d 4045 } 4046 alias X = OpenHashSet!(Alt, FNV!(64, true)); 4047 X x; 4048 assert(!x.contains(Alt.a)); 4049 4050 assert(x.insert(Alt.a) == X.InsertionStatus.added); 4051 4052 assert(x.contains(Alt.a)); 4053 assert(x.containsUsingLinearSearch(Alt.a)); 4054 assert(!x.contains(Alt.b)); 4055 assert(!x.contains(Alt.c)); 4056 assert(!x.contains(Alt.d)); 4057 assert(!x.containsUsingLinearSearch(Alt.b)); 4058 assert(!x.containsUsingLinearSearch(Alt.c)); 4059 assert(!x.containsUsingLinearSearch(Alt.d)); 4060 4061 assert(x.remove(Alt.a)); 4062 assert(!x.contains(Alt.a)); 4063 assert(!x.containsUsingLinearSearch(Alt.a)); 4064 } 4065 4066 /// 4067 @safe pure nothrow 4068 unittest 4069 { 4070 import nxt.digestx.fnv : FNV; 4071 static struct Rel 4072 { 4073 static immutable nullValue = typeof(this).init; 4074 string name; // relation name. WARNING compiler crashes when qualified with `package` 4075 } 4076 alias X = OpenHashSet!(Rel, FNV!(64, true)); 4077 X x; 4078 foreach (const i; 0 .. 100) 4079 { 4080 const char[1] ch = ['a' + i]; 4081 assert(!x.contains(Rel(ch.idup))); 4082 assert(!x.containsUsingLinearSearch(Rel(ch.idup))); 4083 x.insert(Rel(ch.idup)); 4084 assert(x.contains(Rel(ch.idup))); 4085 /* TODO assert(x.containsUsingLinearSearch(Rel(ch.idup))); */ 4086 } 4087 } 4088 4089 /// `SSOString` as set key type 4090 @safe pure nothrow @nogc 4091 unittest 4092 { 4093 import nxt.sso_string : SSOString; 4094 import nxt.digestx.fnv : FNV; 4095 4096 alias K = SSOString; 4097 static assert(isHoleable!K); 4098 alias X = OpenHashSet!(K, FNV!(64, true)); 4099 const n = 100; 4100 4101 X a; 4102 foreach (const i; 0 .. n) 4103 { 4104 const char[1] ch = ['a' + i]; 4105 const k = K(ch); // @nogc 4106 4107 assert(!a.contains(k)); 4108 assert(!a.containsUsingLinearSearch(k)); 4109 4110 assert(a.insert(K(ch)) == X.InsertionStatus.added); 4111 // TODO assert(a.insertAndReturnElement(K(ch)) == k); 4112 assert(a.contains(k)); 4113 assert(a.containsUsingLinearSearch(k)); 4114 4115 assert(a.remove(k)); 4116 assert(!a.contains(k)); 4117 assert(a.insert(K(ch)) == X.InsertionStatus.added); 4118 4119 assert(a.remove(ch[])); 4120 assert(!a.contains(k)); 4121 assert(a.insert(K(ch)) == X.InsertionStatus.added); 4122 } 4123 4124 X b; 4125 foreach (const i; 0 .. n) 4126 { 4127 const char[1] ch = ['a' + (n - 1 - i)]; 4128 const k = K(ch); // @nogc 4129 4130 assert(!b.contains(k)); 4131 assert(!b.containsUsingLinearSearch(k)); 4132 4133 assert(b.insert(K(ch)) == X.InsertionStatus.added); 4134 // TODO assert(b.insertAndReturnElement(K(ch)) == k); 4135 4136 assert(b.contains(k)); 4137 assert(b.containsUsingLinearSearch(k)); 4138 4139 assert(b.remove(k)); 4140 assert(!b.contains(k)); 4141 4142 assert(b.insert(K(ch)) == X.InsertionStatus.added); 4143 } 4144 4145 assert(a == b); 4146 4147 const us = K("_"); 4148 assert(!a.contains(us)); 4149 a ~= us; 4150 assert(a.contains(us)); 4151 } 4152 4153 /// test `opIndexOpAssign` 4154 @safe pure nothrow 4155 unittest 4156 { 4157 import nxt.sso_string : SSOString; 4158 import nxt.digestx.fnv : FNV; 4159 4160 alias K = SSOString; 4161 alias V = long; 4162 alias X = OpenHashMap!(K, V, FNV!(64, true)); 4163 4164 X x; 4165 4166 const a = K("a"); 4167 const b = K("b"); 4168 4169 x[a] = 17; 4170 assert(x[a] == 17); 4171 4172 x[a] += 10; // opIndexOpAssign!("+=") with existing key 4173 assert(x[a] == 27); 4174 4175 x[b] += 10; // opIndexOpAssign!("+=") with non-existing key 4176 assert(x[b] == 10); 4177 4178 x[b] *= 10; // opIndexOpAssign!("*=") with non-existing key 4179 assert(x[b] == 100); 4180 4181 assert(x.length == 2); 4182 4183 assert(x.contains(a)); 4184 assert(x.contains(a[])); 4185 assert(a in x); 4186 assert(a[] in x); 4187 4188 assert(x.contains(b)); 4189 assert(x.contains(b[])); 4190 assert(b in x); 4191 assert(b[] in x); 4192 4193 const c = K("c"); 4194 assert(!x.contains(c)); 4195 assert(!x.contains(c[])); 4196 assert(c !in x); 4197 assert(c[] !in x); 4198 } 4199 4200 /// use prime numbers as capacity 4201 @safe pure unittest 4202 { 4203 import nxt.address : Address; 4204 alias K = Address; 4205 alias V = size_t; 4206 enum bool usePrimeCapacity = false; // TODO enable 4207 alias M = OpenHashMap!(Address, V, 4208 hashOf, 4209 defaultKeyEqualPredOf!K, 4210 Mallocator.instance, 4211 false, 4212 true, 4213 usePrimeCapacity); 4214 M x; 4215 } 4216 4217 /// `SSOString` as map key type 4218 @safe pure nothrow @nogc 4219 unittest 4220 { 4221 import nxt.sso_string : SSOString; 4222 import nxt.digestx.fnv : FNV; 4223 alias K = SSOString; 4224 alias V = long; 4225 alias X = OpenHashMap!(K, V, FNV!(64, true)); 4226 const n = 100; 4227 4228 immutable default_k = K("miss"); 4229 4230 X a; 4231 4232 // insert all 4233 foreach (const i; 0 .. n) 4234 { 4235 const char[1] ch = ['a' + i]; 4236 const k = K(ch); // @nogc 4237 assert(k[] == ch[]); 4238 4239 assert(!a.contains(k)); 4240 assert(!a.contains(ch[])); // @nogc 4241 assert(a.getKeyRef(k, default_k)[] is default_k[]); // on miss use `default_k` 4242 // TODO assert(a.getKeyRef(ch, default_k)[] is default_k[]); // on miss use `default_k` 4243 4244 a[k] = V.init; 4245 4246 assert(a.contains(k)); 4247 assert(a.contains(ch[])); // @nogc 4248 assert(a.getKeyRef(k, default_k)[] !is k[]); // on hit doesn't use `default_k` 4249 assert(a.getKeyRef(k, default_k)[] == ch); 4250 // TODO assert(a.getKeyRef(ch, default_k)[] !is k[]); // on hit doesn't use `default_k` 4251 // assert(a.getKeyRef(ch, default_k)[] == ch); 4252 } 4253 assert(a.length == n); 4254 4255 // remove all 4256 foreach (const i; 0 .. n) 4257 { 4258 const char[1] ch = ['a' + i]; 4259 const k = K(ch); // @nogc 4260 assert(a.contains(k)); 4261 assert(a.remove(k)); 4262 assert(!a.contains(k)); 4263 } 4264 assert(a.length == 0); 4265 4266 // insert all again 4267 foreach (const i; 0 .. n) 4268 { 4269 const char[1] ch = ['a' + i]; 4270 const k = K(ch); // @nogc 4271 assert(k[] == ch[]); 4272 4273 assert(!a.contains(k)); 4274 assert(!a.contains(ch[])); // @nogc 4275 assert(a.getKeyRef(k, default_k)[] is default_k[]); // on miss use `default_k` 4276 // TODO assert(a.getKeyRef(ch, default_k)[] is default_k[]); // on miss use `default_k` 4277 4278 a[k] = V.init; 4279 } 4280 assert(a.length == n); 4281 4282 X b; 4283 foreach (const i; 0 .. n) 4284 { 4285 const char[1] ch = ['a' + (n - 1 - i)]; 4286 const k = K(ch); // @nogc 4287 4288 assert(!b.contains(k)); 4289 4290 b[k] = V.init; 4291 4292 assert(b.contains(k)); 4293 } 4294 4295 assert(a == b); 4296 } 4297 4298 /** Is `true` iff `T` is a memory address (either a `class` or a pointer). */ 4299 enum bool isAddress(T) = (is(T == class) || 4300 (is(T == U*, U) && 4301 // exclude alias this: 4302 !(is(T == struct) || 4303 is(T == union) || 4304 is(T == interface)))); 4305 4306 /** Is `true` iff `T` has a specific value dedicated to representing holes 4307 * (removed/erase) values. 4308 */ 4309 enum isHoleable(T) = (// __traits(hasMember, T, "isHole") && 4310 // __traits(hasMember, T, "holeify") && 4311 __traits(hasMember, T, "holeValue")); 4312 4313 /** Default key equality/equivalence predicate for the type `T`. 4314 */ 4315 template defaultKeyEqualPredOf(T) 4316 { 4317 static if (is(T == class)) 4318 { 4319 // static assert(__traits(hasMember, T, "opEquals"), 4320 // "Type" ~ T.stringof ~ " doesn't have local opEquals() defined"); 4321 // enum defaultKeyEqualPredOf = "a && b && a.opEquals(b)"; 4322 enum defaultKeyEqualPredOf = "a is b"; 4323 // (const T a, const T b) => ((a !is null) && (b !is null) && a.opEquals(b)); 4324 } 4325 else 4326 { 4327 enum defaultKeyEqualPredOf = "a == b"; 4328 } 4329 } 4330 4331 /// 4332 @safe pure nothrow unittest 4333 { 4334 class C 4335 { 4336 @safe pure nothrow @nogc: 4337 this(int x) 4338 { 4339 this.x = x; 4340 } 4341 @property bool opEquals(const scope typeof(this) rhs) const 4342 { 4343 return x == rhs.x; 4344 } 4345 @property override bool opEquals(const scope Object rhs) const @trusted 4346 { 4347 C rhs_ = cast(C)rhs; 4348 return rhs_ && x == rhs_.x; 4349 } 4350 int x; 4351 } 4352 static assert(defaultKeyEqualPredOf!(C) == "a is b"); 4353 } 4354 4355 version(showEntries) import nxt.dbgio : dbg;