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