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