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