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