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