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, K.stringof, " => ", V.stringof); 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 && 712 __traits(isCopyable, T)) /+ TODO: support uncopyable T? +/ 713 { 714 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 715 import std.range.primitives : hasLength; 716 static if (hasLength!R) 717 reserveExtra(elements.length); // might create unused space in `_store` store 718 foreach (ref element; elements) { 719 static if (!hasLength!R) 720 reserveExtra(1); 721 static if (hasIndirections!T) 722 insertWithoutGrowthNoStatus(element); 723 else 724 insertWithoutGrowthNoStatus(*cast(Unqual!T*)&element); 725 } 726 } 727 728 /// Is `true` iff in-place rehashing during growth should be performed. 729 enum bool growInPlaceFlag = false; /+ TODO: warning `growInPlaceWithCapacity` is buggy +/ 730 731 /// Numerator for grow scale. 732 enum growScaleP = 3; 733 /// Denominator for grow scale. 734 enum growScaleQ = 2; 735 736 /** Reserve room for `extraCapacity` number of extra buckets. */ 737 void reserveExtra(in size_t extraCapacity) { /*!tlm*/ 738 version (LDC) pragma(inline, true); 739 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 740 immutable newCapacity = (_count + extraCapacity)*growScaleP/growScaleQ; 741 if (newCapacity > _store.elts.length) 742 growWithNewCapacity(newCapacity); 743 } 744 745 /// Grow (rehash) to make for `newCapacity` number of elements. 746 private void growWithNewCapacity()(in size_t newCapacity) /*tlm*/ { 747 version (unittest) assert(newCapacity > _store.elts.length); 748 static if (__traits(hasMember, Allocator, "reallocate")) { 749 static if (growInPlaceFlag) 750 growInPlaceWithCapacity(newCapacity); 751 else 752 growStandardWithNewCapacity(newCapacity); 753 } else 754 growStandardWithNewCapacity(newCapacity); 755 } 756 757 /// Grow (rehash) store to make room for `newCapacity` number of elements. 758 private void growStandardWithNewCapacity()(in size_t newCapacity) /*tlm*/ { 759 version (unittest) assert(newCapacity > _store.elts.length); 760 auto next = typeof(this).withCapacity(newCapacity); 761 foreach (immutable i, ref bin; _store.elts) 762 if (isOccupiedAtIndex(i)) { 763 next.insertMoveWithoutGrowth(bin); // value is zeroed but 764 static if (!hasHoleableKey) 765 keyOf(bin).nullify(); // keyC must zeroed 766 } 767 move(next, this); 768 } 769 770 /// Tag as lazily delete element at index `index`.` 771 private void tagAsLazilyDeletedElementAtIndex(in size_t index) { 772 version (LDC) pragma(inline, true); 773 // key 774 static if (options.linearSearchMaxSize != 0) 775 if (_store.elts.length * T.sizeof <= options.linearSearchMaxSize) { 776 keyOf(_store.elts[index]).nullify(); 777 goto done; 778 } 779 static if (hasHoleableKey) 780 keyOf(_store.elts[index]) = holeKeyConstant; 781 else { 782 keyOf(_store.elts[index]).nullify(); 783 tagHoleAtIndex(index); 784 } 785 done: 786 // value 787 static if (hasValue) { 788 static if (hasElaborateDestructor!V) // if we should clear all 789 .destroy(valueOf(_store.elts[index])); 790 static if (mustAddGCRange!V) // if we should clear all 791 valueOf(_store.elts[index]) = V.init; // avoid GC mark-phase dereference 792 } 793 } 794 795 /// Insert `element` at `index`. 796 private void insertElementAtIndex(SomeElement)(scope SomeElement element, in size_t index) @trusted /*tlm*/ { 797 version (LDC) pragma(inline, true); 798 static if (isSlice!SomeElement && 799 !is(typeof(SomeElement.init[0]) == immutable)) { 800 /* key is an array of non-`immutable` elements which cannot safely 801 * be stored because keys must be immutable for hashing to work 802 * properly, therefore duplicate */ 803 keyOf(_store.elts[index]) = element.idup; 804 } else { 805 static if (__traits(isPOD, SomeElement)) 806 _store.elts[index] = element; 807 else { 808 static if (__traits(isPOD, K)) 809 keyOf(_store.elts[index]) = keyOf(element); 810 else 811 move(keyOf(element), 812 keyOf(_store.elts[index])); 813 814 static if (hasValue) { 815 import core.lifetime : moveEmplace; 816 moveEmplace(valueOf(element), 817 valueOf(_store.elts[index])); 818 } 819 } 820 } 821 } 822 823 /// Rehash elements in-place. 824 private void rehashInPlace()() @trusted /*tlm*/ { 825 import core.bitop : bts, bt; 826 import nxt.array_help : makeBitArrayZeroed, wordCountOfBitCount; 827 828 size_t* dones = makeBitArrayZeroed!Allocator(_store.elts.length); 829 830 foreach (const doneIndex; 0 .. _store.elts.length) { 831 if (bt(dones, doneIndex)) { continue; } // if _store.elts[doneIndex] continue 832 if (isOccupiedAtIndex(doneIndex)) { 833 import core.lifetime : moveEmplace; 834 T currentElement = void; 835 836 /+ TODO: functionize: +/ 837 moveEmplace(_store.elts[doneIndex], currentElement); 838 static if (is(K == Nullable!(_), _)) 839 keyOf(_store.elts[doneIndex]).nullify(); // `moveEmplace` doesn't init source of type Nullable 840 841 while (true) { 842 // TODO remove param `element` 843 alias pred = (in index, in element) 844 => (!isOccupiedAtIndex(index) || // free slot 845 !bt(dones, index)); // or a not yet replaced element 846 static if (usePrimeCapacity) 847 immutable hitIndex = xxx; 848 else 849 immutable hitIndex = _store.elts[].triangularProbeFromIndex!(pred, assumeNonFullHaystack)(keyToIndex(keyOf(currentElement))); 850 assert(hitIndex != _store.elts.length, "no free slot"); 851 852 bts(dones, hitIndex); // _store.elts[hitIndex] will be at it's correct position 853 854 if (isOccupiedAtIndex(doneIndex)) { 855 T nextElement = void; 856 857 /+ TODO: functionize: +/ 858 moveEmplace(_store.elts[hitIndex], nextElement); // save non-free slot 859 static if (is(K == Nullable!(_), _)) 860 keyOf(_store.elts[hitIndex]).nullify(); // `moveEmplace` doesn't init source of type Nullable 861 862 moveEmplace(currentElement, _store.elts[hitIndex]); 863 moveEmplace(nextElement, currentElement); 864 } else { // if no free slot 865 moveEmplace(currentElement, _store.elts[hitIndex]); 866 break; // inner iteration is finished 867 } 868 } 869 } 870 bts(dones, doneIndex); // _store.elts[doneIndex] is at it's correct position 871 } 872 873 allocator.deallocate(cast(void[])(dones[0 .. wordCountOfBitCount(_store.elts.length)])); 874 } 875 876 /** Grow (with rehash) store in-place making room for `minimumCapacity` number of elements. */ 877 private void growInPlaceWithCapacity()(in size_t minimumCapacity) @trusted /*tlm*/ { 878 assert(minimumCapacity > _store.elts.length); 879 static if (usePrimeCapacity) 880 immutable newCapacity = ceilingPrime(minimumCapacity, _primeIndex); 881 else 882 immutable newCapacity = nextPow2(minimumCapacity); 883 immutable newByteCount = T.sizeof*newCapacity; 884 const oldStorePtr = _store.elts.ptr; 885 immutable oldLength = _store.elts.length; 886 auto rawStore = cast(void[])_store; 887 if (allocator.reallocate(rawStore, newByteCount)) { 888 _store = cast(T[])rawStore; 889 static if (mustAddGCRange!T) { 890 if (oldStorePtr !is null) 891 gc_removeRange(oldStorePtr); // `gc_removeRange` fails for null input 892 gc_addRange(_store.elts.ptr, newByteCount); 893 } 894 /+ TODO: make this an array operation `nullifyAll` or `nullifyN` +/ 895 foreach (ref bin; _store.elts[oldLength .. newCapacity]) 896 keyOf(bin).nullify(); // move this `init` to reallocate() above? 897 rehashInPlace(); 898 } else 899 assert(0, "couldn't reallocate bin"); 900 } 901 902 /** Insert (without growth) `element` at `hitIndex`. */ 903 private InsertionStatus insertWithoutGrowth(SomeElement)(in SomeElement element, /*tlm*/ 904 out size_t hitIndex) @trusted { 905 version (LDC) pragma(inline, true); 906 version (unittest) { 907 assert(!keyOf(element).isNull); 908 static if (hasHoleableKey) { assert(!isHoleKeyConstant(adjustKey(keyOf(element)))); } 909 } 910 size_t holeIndex = size_t.max; // first hole index to written to if hole found 911 const hitIndexPrel = indexOfKeyOrVacancyAndFirstHole(keyOf(element), holeIndex); 912 if (hitIndexPrel == _store.elts.length || // keys miss and holes may have filled all empty slots 913 keyOf(_store.elts[hitIndexPrel]).isNull) // just key miss but a hole may have been found on the way 914 { 915 immutable hasHole = holeIndex != size_t.max; // hole was found along the way 916 if (hasHole) 917 hitIndex = holeIndex; // pick hole instead 918 else 919 hitIndex = hitIndexPrel; // normal hit 920 version (unittest) assert(hitIndex != _store.elts.length, "no null or hole slot"); 921 static if (__traits(isPOD, SomeElement)) 922 insertElementAtIndex(*cast(SomeElement*)&element, hitIndex); 923 else 924 insertElementAtIndex(move(*cast(SomeElement*)&element), hitIndex); 925 static if (!hasHoleableKey) 926 if (hasHole) 927 untagHoleAtIndex(hitIndex); 928 _count = _count + 1; 929 return InsertionStatus.added; 930 } 931 else 932 hitIndex = hitIndexPrel; 933 static if (hasValue) { 934 static if (__traits(isStaticArray, V)) 935 // identity comparison of static arrays implicitly coerces them 936 // to slices, which are compared by reference, so don't use !is here 937 immutable valueDiffers = (valueOf(element) != 938 valueOf(_store.elts[hitIndexPrel])); // only value changed 939 else 940 immutable valueDiffers = (valueOf(element) !is 941 valueOf(_store.elts[hitIndexPrel])); // only value changed 942 if (valueDiffers) { // only value changed 943 move(valueOf(*cast(SomeElement*)&element), 944 valueOf(_store.elts[hitIndexPrel])); // value is defined so overwrite it 945 return InsertionStatus.modified; 946 } 947 } 948 return InsertionStatus.unmodified; 949 } 950 951 /** Insert (without growth) `element` and return index to bin where insertion happended. */ 952 private size_t insertWithoutGrowthNoStatus(SomeElement)(in SomeElement element) @trusted /*tlm*/ { 953 version (LDC) pragma(inline, true); 954 version (unittest) { 955 assert(!keyOf(element).isNull); 956 static if (hasHoleableKey) { assert(!isHoleKeyConstant(adjustKey(keyOf(element)))); } 957 } 958 size_t hitIndex = 0; 959 size_t holeIndex = size_t.max; // first hole index to written to if hole found 960 const hitIndexPrel = indexOfKeyOrVacancyAndFirstHole(adjustKey(keyOf(element)), holeIndex); 961 if (hitIndexPrel == _store.elts.length || // keys miss and holes may have filled all empty slots 962 keyOf(_store.elts[hitIndexPrel]).isNull) // just key miss but a hole may have been found on the way 963 { 964 immutable hasHole = holeIndex != size_t.max; // hole was found along the way 965 if (hasHole) 966 hitIndex = holeIndex; // pick hole instead 967 else 968 hitIndex = hitIndexPrel; // normal hit 969 version (unittest) assert(hitIndex != _store.elts.length, "no null or hole slot"); 970 static if (__traits(isPOD, SomeElement)) 971 insertElementAtIndex(*cast(SomeElement*)&element, hitIndex); 972 else 973 insertElementAtIndex(move(*cast(SomeElement*)&element), hitIndex); 974 static if (!hasHoleableKey) 975 if (hasHole) { untagHoleAtIndex(hitIndex); } 976 _count = _count + 1; 977 return hitIndex; 978 } 979 else 980 hitIndex = hitIndexPrel; 981 static if (hasValue) 982 // modify existing value 983 move(valueOf(*cast(SomeElement*)&element), 984 valueOf(_store.elts[hitIndexPrel])); // value is defined so overwrite it 985 return hitIndex; 986 } 987 988 /** Insert `element`, being either a key-value (map-case) or a just a key (set-case). 989 */ 990 private InsertionStatus insertMoveWithoutGrowth()(ref T element) /*tlm*/ { 991 version (LDC) pragma(inline, true); 992 size_t hitIndex = 0; 993 return insertWithoutGrowth(move(element), hitIndex); 994 } 995 996 static if (hasValue) { 997 /** Insert or replace `value` at `key`. */ 998 InsertionStatus insert()(K key, V value) /*tlm*/ { 999 pragma(inline, true); // LDC must have this 1000 static if (__traits(isPOD, K)) { 1001 static if (__traits(isPOD, V)) 1002 return insert(T(key, value)); 1003 else 1004 return insert(T(key, move(value))); 1005 } else { 1006 static if (__traits(isPOD, V)) 1007 return insert(T(move(key), value)); 1008 else 1009 return insert(T(move(key), move(value))); 1010 } 1011 } 1012 } 1013 1014 static if (!hasValue) { 1015 scope const(K)* opBinaryRight(string op, SomeKey)(in SomeKey key) const return @trusted 1016 if (op == `in` && 1017 isScopedKeyType!(typeof(key))) 1018 in(isValidKey(key)) { 1019 version (D_Coverage) {} else pragma(inline, true); 1020 static if (hasHoleableKey) { assert(!isHoleKeyConstant(cast(K)adjustKey(key))); } 1021 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKey(key)); // cast scoped `key` is @trusted 1022 immutable match = hitIndex != _store.elts.length && isOccupiedAtIndex(hitIndex); 1023 if (match) 1024 return &_store.elts[hitIndex]; 1025 else 1026 return null; 1027 } 1028 1029 ref typeof(this) opOpAssign(string op, SomeKey)(in SomeKey key) return @trusted 1030 if (op == `~` && // binary assignment operator `~=` 1031 isScopedKeyType!(typeof(key))) { 1032 version (LDC) pragma(inline, true); 1033 reserveExtra(1); 1034 immutable hitIndex = insertWithoutGrowthNoStatus(key); 1035 return this; 1036 } 1037 1038 /** Try to retrieve `class`-element of type `Class` constructed with 1039 * parameters `params`. 1040 * 1041 * Typically used to implement (polymorphic) caching of class-types 1042 * without the need for GG-allocating a temporary instance of a 1043 * `class`-element potentially already stored in `this` set. 1044 * 1045 * Polymorphic caching can be realized by setting `hasher` to 1046 * `hash_functions.hashOfPolymorphic`. 1047 */ 1048 scope const(Class) tryGetElementFromCtorParams(Class, Params...)(scope Params params) const return @trusted 1049 if (is(Class : K)) { 1050 import core.lifetime : emplace; 1051 void[__traits(classInstanceSize, Class)] tempNode_ = void; 1052 scope Class temp = emplace!(Class)(tempNode_, params); 1053 Class* hit = cast(Class*)(temp in this); 1054 1055 static if (__traits(hasMember, Class, "__dtor")) 1056 temp.__dtor(); 1057 1058 if (hit) { 1059 auto typedHit = cast(typeof(return))*hit; 1060 assert(typedHit, "Expected class " ~ Class.stringof ~ " but got hit was of other type"); /+ TODO: give warning or throw +/ 1061 return typedHit; 1062 } 1063 return null; 1064 } 1065 } 1066 1067 static if (hasValue) { 1068 scope inout(V)* opBinaryRight(string op, SomeKey)(in SomeKey key) inout return @trusted // `auto ref` here makes things slow 1069 if (op == `in` && 1070 isScopedKeyType!(SomeKey)) { 1071 version (LDC) pragma(inline, true); 1072 // pragma(msg, SomeKey, " => ", K); 1073 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(cast(const(K))adjustKey(key)); // cast scoped `key` is @trusted 1074 immutable match = hitIndex != _store.elts.length && isOccupiedAtIndex(hitIndex); 1075 if (match) 1076 return cast(typeof(return))&_store.elts[hitIndex].value; 1077 else 1078 return null; 1079 } 1080 1081 /// Indexing. 1082 scope ref inout(V) opIndex(SomeKey)(in SomeKey key) inout return @trusted // `auto ref` here makes things slow. 1083 if (isScopedKeyType!(typeof(key))) { 1084 import core.exception : onRangeError; 1085 version (LDC) pragma(inline, true); 1086 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKey(key)); // cast scoped `key` is @trusted 1087 immutable match = hitIndex != _store.elts.length && isOccupiedAtIndex(hitIndex); 1088 if (!match) 1089 onRangeError(); 1090 return _store.elts[hitIndex].value; 1091 } 1092 1093 /** Get value of `key` or `defaultValue` if `key` not present (and 1094 * therefore `nothrow`). 1095 * 1096 * Returns: value reference iff `defaultValue` is an l-value. 1097 * 1098 * TODO: make `defaultValue` `lazy` when that can be `nothrow` 1099 */ 1100 auto ref inout(V) get()(in K key, auto ref inout(V) defaultValue) inout /*tlm*/ { 1101 version (LDC) pragma(inline, true); 1102 if (auto valuePtr = key in this) 1103 return *valuePtr; 1104 else 1105 return defaultValue; 1106 } 1107 1108 /** Get reference to `key`-part of stored element at `key`, if present, 1109 * otherwise return `defaultKey`. 1110 * 1111 * Used to implement caching inside the key part of a map. 1112 */ 1113 ref const(K) getKeyRef(SomeKey)(in SomeKey key, ref const(K) defaultKey) const return @trusted @nogc 1114 if (isScopedKeyType!(SomeKey)) { 1115 version (LDC) pragma(inline, true); 1116 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKey(key)); // cast scoped `key` is @trusted 1117 immutable match = hitIndex != _store.elts.length && isOccupiedAtIndex(hitIndex); 1118 if (match) 1119 return _store.elts[hitIndex].key; 1120 return defaultKey; 1121 } 1122 1123 /** Supports the syntax `aa[key] = value;`. 1124 */ 1125 ref V opIndexAssign()(V value, K key) /* tlm. TODO: return scope */ 1126 in(isValidKey(key)) { 1127 version (LDC) pragma(inline, true); 1128 static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(key)); } 1129 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 1130 reserveExtra(1); 1131 static if (__traits(isPOD, K)) { 1132 static if (__traits(isPOD, V)) 1133 immutable hitIndex = insertWithoutGrowthNoStatus(T(key, value)); 1134 else 1135 immutable hitIndex = insertWithoutGrowthNoStatus(T(key, move(value))); 1136 } else { 1137 static if (__traits(isPOD, V)) 1138 immutable hitIndex = insertWithoutGrowthNoStatus(T(move(key), value)); 1139 else 1140 immutable hitIndex = insertWithoutGrowthNoStatus(T(move(key), move(value))); 1141 } 1142 return _store.elts[hitIndex].value; 1143 } 1144 1145 ref V opIndexOpAssign(string op, Rhs)(Rhs rhs, K key) /+ TODO: return scope +/ 1146 // if (true) /+ TODO: pre-check that mixin will work +/ 1147 in(isValidKey(key)) { 1148 // pragma(msg, "opIndexOpAssign: Key:", K, " Value:", V, " Rhs:", Rhs, " op:", op); 1149 static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(key)); } 1150 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 1151 reserveExtra(1); 1152 size_t holeIndex = size_t.max; // first hole index to written to if hole found 1153 immutable hitIndex = indexOfKeyOrVacancyAndFirstHole(key, holeIndex); 1154 if (hitIndex == _store.elts.length || // keys miss and holes may have filled all empty slots 1155 keyOf(_store.elts[hitIndex]).isNull) // just key miss but a hole may have been found on the way 1156 { 1157 immutable hasHole = holeIndex != size_t.max; // hole was found along the way 1158 immutable index = (hasHole ? 1159 holeIndex : // pick hole instead 1160 hitIndex); // normal hit 1161 version (unittest) assert(index != _store.elts.length, "no null or hole slot"); 1162 static if (__traits(isCopyable, K)) { 1163 static if (op == "~" || 1164 op == "+" || 1165 op == "*") { 1166 static if (is(V : Rhs[])) // isDynamicArray of `Rhs` 1167 insertElementAtIndex(T(key, [rhs]), /+ TODO: if `V(rhs)` is not supported use `V.init` followed by `OP= rhs` +/ 1168 index); 1169 else 1170 // dbg("opIndexOpAssign-new: k:", key, " rhs:", rhs); 1171 insertElementAtIndex(T(key, V(rhs)), /+ TODO: if `V(rhs)` is not supported use `V.init` followed by `OP= rhs` +/ 1172 index); 1173 } 1174 else 1175 static assert(0, "Handel op " ~ op); 1176 } 1177 else 1178 static assert(0, "Handle uncopyable key " ~ K.stringof); 1179 // insertElementAtIndex(move(*cast(SomeElement*)&element), index); 1180 static if (!hasHoleableKey) 1181 if (hasHole) { untagHoleAtIndex(index); } 1182 _count = _count + 1; 1183 return _store.elts[index].value; 1184 } 1185 else { // `key`-hit at index `hitIndex` 1186 // dbg("opIndexOpAssign-mod: k:", key, " rhs:", rhs); 1187 mixin(`return _store.elts[hitIndex].value ` ~ op ~ `= rhs;`); // modify existing value 1188 } 1189 } 1190 } 1191 1192 static if (!options.growOnly) { 1193 /** Remove `element`. 1194 * Returns: `true` if element was removed, `false` otherwise. 1195 */ 1196 bool remove(SomeKey)(in SomeKey key) scope /*tlm*/ 1197 if (isScopedKeyType!(typeof(key))) { 1198 version (LDC) pragma(inline, true); 1199 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 1200 static if (options.linearSearchMaxSize != 0) 1201 if (_store.elts.length * T.sizeof <= options.linearSearchMaxSize) { 1202 foreach (immutable i, const ref element; _store.elts) // linear search is faster for small arrays 1203 if (keyEqualPredFn(keyOf(element), key)) { 1204 tagAsLazilyDeletedElementAtIndex(i); 1205 _count = _count - 1; 1206 return true; 1207 } 1208 return false; 1209 } 1210 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(cast(const(K))adjustKey(key)); 1211 immutable match = hitIndex != _store.elts.length && isOccupiedAtIndex(hitIndex); 1212 if (match) { 1213 tagAsLazilyDeletedElementAtIndex(hitIndex); 1214 _count = _count - 1; 1215 return true; 1216 } 1217 return false; 1218 } 1219 1220 import nxt.traits_ex : isRefIterable; 1221 import std.range.primitives : front; 1222 1223 /** Remove all elements matching `keys` followed by a rehash. 1224 * 1225 * Returns: number of elements that were removed. 1226 */ 1227 version (none) /+ TODO: enable +/ 1228 size_t rehashingRemoveN(Keys)(in Keys keys) /*tlm*/ 1229 if (isRefIterable!Keys && 1230 is(typeof(Keys.front == K.init))) { 1231 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 1232 rehash!("!a.isNull && keys.canFind(a)")(); /+ TODO: make this work +/ 1233 return 0; 1234 } 1235 } 1236 1237 /// Check if empty. 1238 @property bool empty() const => _count == 0; 1239 /// Get length. 1240 @property size_t length() const => _count; 1241 /// Get element count. 1242 alias count = length; 1243 /// Get bin count (capacity). 1244 @property size_t binCount() const => _store.elts.length; 1245 1246 /** Returns: get total probe count for all elements stored. */ 1247 size_t totalProbeCount()() const /*tlm*/ { 1248 static if (hasValue) 1249 auto range = byKeyValue(this); 1250 else 1251 auto range = byElement(this); 1252 typeof(return) totalCount = 0; 1253 foreach (const ref currentElement; range) { 1254 static if (__traits(isCopyable, T)) // TODO why does using `passElementByValue` fail as an expression for certain element types? 1255 /* don't use `auto ref` for copyable `T`'s to prevent 1256 * massive performance drop for small elements when compiled 1257 * with LDC. TODO: remove when LDC is fixed. */ 1258 alias pred = (const scope element) => (keyEqualPredFn(keyOf(element), 1259 keyOf(currentElement))); 1260 else 1261 alias pred = (const scope ref element) => (keyEqualPredFn(keyOf(element), 1262 keyOf(currentElement))); 1263 static if (usePrimeCapacity) 1264 immutable probeCount = xxx; 1265 else 1266 immutable probeCount = triangularProbeCountFromIndex!(pred)(_store.elts[], keyToIndex(keyOf(currentElement))); 1267 totalCount += probeCount; 1268 } 1269 return totalCount; 1270 } 1271 1272 /** Returns: average probe count for all elements stored. */ 1273 double averageProbeCount()() const /*tlm*/ => (cast(typeof(return))totalProbeCount)/length; 1274 1275 /** Unsafe access to raw store. 1276 * 1277 * Needed by wrapper containers such as `SSOHybridHashSet`. 1278 */ 1279 pragma(inline, true) 1280 inout(T)[] rawStore() inout @system pure nothrow @nogc => _store.elts; 1281 1282 static if (hasHoleableKey) { 1283 static bool isOccupiedBin(in T bin) => (keyOf(bin).isNull && !isHoleKeyConstant(keyOf(bin))); 1284 } 1285 1286 private: 1287 import nxt.allocator_traits : AllocatorState; 1288 mixin AllocatorState!Allocator; // put first as emsi-containers do 1289 1290 struct Store { 1291 /** Make store with `capacity` number of slots. 1292 * 1293 * If `initFlag` is true then initialize the elements. 1294 */ 1295 static typeof(this) withCapacity(in size_t capacity, in bool initFlag) @trusted pure nothrow @nogc 1296 { 1297 static if (usePrimeCapacity) { 1298 /+ TODO: check that capacity is prime? +/ 1299 } else { 1300 debug import std.math : isPowerOf2; 1301 debug assert(capacity.isPowerOf2); // quadratic probing needs power of two capacity (`_store.elts.length`) 1302 } 1303 /+ TODO: cannot use makeArray here because it cannot handle uncopyable types +/ 1304 // import std.experimental.allocator : makeArray; 1305 // auto store = allocator.makeArray!T(capacity, nullKeyElement); 1306 import nxt.bit_traits : isAllZeroBits; 1307 immutable eltByteCount = T.sizeof*capacity; 1308 static if (!hasHoleableKey) { 1309 immutable holeByteCount = binBlockBytes(capacity); 1310 immutable totalByteCount = eltByteCount + holeByteCount; 1311 } else 1312 immutable totalByteCount = eltByteCount; 1313 static if (hasAddressLikeKey || 1314 (__traits(isZeroInit, K) && 1315 __traits(hasMember, K, "nullifier")) || 1316 /+ TODO: add check for __traits(isZeroInit, K) and member `K.nullValue` == `K.init` +/ 1317 (__traits(hasMember, K, `nullValue`) && // if key has a null value 1318 __traits(compiles, { enum _ = isAllZeroBits!(K, K.nullValue); }) && // prevent strange error given when `K` is `knet.data.Data` 1319 isAllZeroBits!(K, K.nullValue))) // check that it's zero bits only 1320 { 1321 /+ TODO: use std.experimental.allocator.makeArray instead of this which handles clever checking for isZeroInit +/ 1322 import nxt.container.traits : makeInitZeroArray; 1323 static if (__traits(hasMember, typeof(allocator), "allocateZeroed") && 1324 is(typeof(allocator.allocateZeroed(totalByteCount)))) 1325 auto rawStore = allocator.allocateZeroed(totalByteCount); 1326 else { 1327 auto rawStore = allocator.allocate(totalByteCount); 1328 (cast(ubyte[])rawStore)[] = 0; /+ TODO: is this the most efficient way? +/ 1329 } 1330 if (rawStore.ptr is null && 1331 capacity >= 1) 1332 onOutOfMemoryError(); 1333 static if (!hasHoleableKey) { 1334 auto holes = rawStore[eltByteCount .. totalByteCount]; /+ TODO: currently unused +/ 1335 } 1336 auto store = typeof(this)(cast(T[])rawStore[0 .. eltByteCount]); 1337 } else { // when default null key is not represented by zeros 1338 // pragma(msg, "emplace:", "K:", K, " V:", V); 1339 auto rawStore = allocator.allocate(totalByteCount); 1340 if (rawStore.ptr is null && 1341 totalByteCount >= 1) 1342 onOutOfMemoryError(); 1343 auto store = typeof(this)(cast(T[])rawStore[0 .. eltByteCount]); 1344 static if (!hasHoleableKey) { 1345 size_t[] holes = (cast(size_t*)(store.elts.ptr + store.elts.length))[0 .. holesWordCount(capacity)]; 1346 holes[] = 0; /+ TODO: is this the most efficient way? +/ 1347 } 1348 if (initFlag) { 1349 foreach (ref bin; store.elts) { 1350 import core.lifetime : emplace; 1351 enum hasNullValueKey = __traits(hasMember, K, `nullValue`); 1352 static if (hasNullValueKey && 1353 !is(typeof(emplace(&keyOf(bin), K.nullValue)))) // __traits(compiles) fails here when building knet 1354 pragma(msg, __FILE__, ":", __LINE__, ":warning: emplace fails for null-Value key type ", K); 1355 // initialize key 1356 static if (hasNullValueKey && 1357 is(typeof(emplace(&keyOf(bin), K.nullValue)))) 1358 emplace(&keyOf(bin), K.nullValue); // initialize in-place with explicit `K.nullValue` 1359 else { 1360 emplace(&keyOf(bin)); // initialize in-place with default value 1361 keyOf(bin).nullify(); // moveEmplace doesn't init source of type `Nullable` 1362 } 1363 // initialize value 1364 static if (hasValue) { 1365 static if (hasElaborateDestructor!V) 1366 emplace(&valueOf(bin)); // initialize in-place 1367 else static if (mustAddGCRange!V) 1368 valueOf(bin) = V.init; 1369 else {} // ok for this case to have uninitialized value part 1370 } 1371 } 1372 } 1373 } 1374 static if (mustAddGCRange!T) 1375 gc_addRange(store.elts.ptr, eltByteCount); 1376 return store; 1377 } 1378 1379 static if (hasFunctionAttributes!(allocator.allocate, "@nogc")) { 1380 import nxt.gc_traits : NoGc; 1381 @NoGc T[] elts; // one element per bin 1382 } else 1383 T[] elts; // one element per bin 1384 static if (!hasHoleableKey) 1385 inout(size_t)* holesPtr() inout @property @trusted pure nothrow @nogc => cast(size_t*)(elts.ptr + elts.length); 1386 } 1387 Store _store; 1388 1389 static if (usePrimeCapacity) 1390 PrimeIndex _primeIndex = PrimeIndex.init; 1391 1392 size_t _count; // total number of (non-null) elements stored in `_store` 1393 1394 static if (isBorrowChecked) { 1395 debug { // use Rust-style borrow checking at run-time 1396 size_t _borrowCount; 1397 1398 /// Number of bits needed to store number of read borrows. 1399 enum borrowCountBits = 8*isBorrowChecked.sizeof; 1400 1401 /// Maximum value possible for `_borrowCount`. 1402 enum borrowCountMax = 2^^borrowCountBits - 1; 1403 1404 version (none) { 1405 /// Number of bits needed to store number of read borrows. 1406 enum borrowCountBits = 24; 1407 1408 /// Maximum value possible for `_borrowCount`. 1409 enum borrowCountMax = 2^^borrowCountBits - 1; 1410 1411 import std.bitmanip : bitfields; 1412 mixin(bitfields!(size_t, "_count", 8*size_t.sizeof - borrowCountBits, 1413 uint, "_borrowCount", borrowCountBits)); 1414 } 1415 1416 pragma(inline, true): 1417 pure nothrow @safe @nogc: 1418 1419 @property { 1420 /// Returns: `true` iff `this` is borrowed (either read or write). 1421 bool isBorrowed() const => _borrowCount >= 1; 1422 /// Returns: number of borrowers of `this` (both read and write). 1423 auto borrowCount() const => _borrowCount; 1424 } 1425 1426 /// Increase borrow count. 1427 void incBorrowCount() 1428 in(_borrowCount + 1 != borrowCountMax) { 1429 _borrowCount = _borrowCount + 1; 1430 } 1431 1432 /// Decrease borrow count. 1433 void decBorrowCount() 1434 in(_borrowCount != 0) { 1435 _borrowCount = _borrowCount - 1; 1436 } 1437 } 1438 } 1439 1440 /** Returns: bin index of `key`. */ 1441 private size_t keyToIndex(SomeKey)(in SomeKey key) const @trusted { 1442 version (LDC) pragma(inline, true); /+ TODO: inline always +/ 1443 1444 /** Returns: current index mask from bin count `_store.elts.length`. 1445 * TODO: Inline this and check for speed-up. 1446 */ 1447 static size_t powerOf2Mask(in size_t length) pure nothrow @safe @nogc { 1448 version (unittest) { 1449 /+ TODO: move to in contract: +/ 1450 debug import std.math : isPowerOf2; 1451 debug assert(length.isPowerOf2); // quadratic probing needs power of two capacity (`_store.elts.length`) 1452 } else { 1453 version (D_Coverage) {} else pragma(inline, true); 1454 } 1455 return length - 1; 1456 } 1457 1458 static if (is(typeof(hasher(key)) == hash_t)) // for instance when hasher being `hashOf` 1459 immutable hash = hasher(key); 1460 else static if (is(hasher == struct) || // such as `FNV` 1461 is(hasher == class)) { 1462 import nxt.digestion : hashOf2; 1463 immutable hash = hashOf2!(hasher)(key); 1464 } else 1465 static assert(false, "Unsupported hasher of type " ~ typeof(hasher).stringof); 1466 static if (usePrimeCapacity) 1467 return moduloPrimeIndex(hash, _primeIndex); 1468 else 1469 return hash & powerOf2Mask(_store.elts.length); 1470 } 1471 1472 /** Find index to `key` if it exists or to first empty slot found, skipping 1473 * (ignoring) lazily deleted slots. 1474 */ 1475 private size_t indexOfKeyOrVacancySkippingHoles(in K key) const @trusted scope { // `auto ref` here makes things slow 1476 /+ TODO: if (...) +/ 1477 version (LDC) pragma(inline, true); 1478 version (unittest) { 1479 assert(!key.isNull); 1480 static if (hasHoleableKey) { assert(!isHoleKeyConstant(key)); } 1481 } 1482 static if (options.linearSearchMaxSize != 0) 1483 if (_store.elts.length * T.sizeof <= options.linearSearchMaxSize) { 1484 foreach (immutable i, const ref element; _store.elts) // linear search is faster for small arrays 1485 if ((keyOf(element).isNull || 1486 keyEqualPredFn(keyOf(element), key))) 1487 return i; 1488 return _store.elts.length; 1489 } 1490 static if (hasHoleableKey) 1491 alias pred = (in element) => (keyOf(element).isNull || 1492 keyEqualPredFn(keyOf(element), key)); 1493 else 1494 alias pred = (in index, 1495 in element) => (!hasHoleAtPtrIndex(_store.holesPtr, index) && 1496 (keyOf(element).isNull || 1497 keyEqualPredFn(keyOf(element), key))); 1498 static if (usePrimeCapacity) 1499 return xxx; 1500 else 1501 return _store.elts[].triangularProbeFromIndex!(pred, assumeNonFullHaystack)(keyToIndex(key)); 1502 } 1503 1504 private size_t indexOfKeyOrVacancyAndFirstHole(in K key, // `auto ref` here makes things slow 1505 ref size_t holeIndex) const @trusted scope 1506 { 1507 version (LDC) pragma(inline, true); 1508 version (unittest) { 1509 assert(!key.isNull); 1510 static if (hasHoleableKey) { assert(!isHoleKeyConstant(key)); } 1511 } 1512 static if (options.linearSearchMaxSize != 0) 1513 if (_store.elts.length * T.sizeof <= options.linearSearchMaxSize) { 1514 foreach (immutable i, const ref element; _store.elts) // linear search is faster for small arrays 1515 if ((keyOf(element).isNull || 1516 keyEqualPredFn(keyOf(element), key))) 1517 return i; 1518 return _store.elts.length; 1519 } 1520 static if (hasHoleableKey) { 1521 alias hitPred = (in element) => (keyOf(element).isNull || 1522 keyEqualPredFn(keyOf(element), key)); 1523 alias holePred = (in element) => (isHoleKeyConstant(keyOf(element))); 1524 } else { 1525 alias hitPred = (in index, 1526 in element) => (!hasHoleAtPtrIndex(_store.holesPtr, index) && 1527 (keyOf(element).isNull || 1528 keyEqualPredFn(keyOf(element), key))); 1529 alias holePred = (in index, /+ TODO: use only index +/ 1530 in element) => (hasHoleAtPtrIndex(_store.holesPtr, index)); 1531 } 1532 static if (usePrimeCapacity) 1533 return xxx; 1534 else 1535 return _store.elts[].triangularProbeFromIndexIncludingHoles!(hitPred, holePred, assumeNonFullHaystack)(keyToIndex(key), holeIndex); 1536 } 1537 1538 /// Returns: `true` iff `index` indexes a non-null element, `false` otherwise. 1539 private bool isOccupiedAtIndex(in size_t index) const { 1540 version (LDC) pragma(inline, true); 1541 version (unittest) assert(index < _store.elts.length); 1542 if (keyOf(_store.elts[index]).isNull) { return false; } 1543 static if (hasHoleableKey) 1544 return !isHoleKeyConstant(keyOf(_store.elts[index])); 1545 else 1546 return !hasHoleAtPtrIndex(_store.holesPtr, index); 1547 } 1548 } 1549 1550 /** Duplicate `src` into uninitialized `dst` ignoring prior destruction of `dst`. 1551 * 1552 * TODO: Move to a more generic place either in phobos-next or Phobos. 1553 */ 1554 static private void duplicateEmplace(T)(in T src, 1555 scope ref T dst) @system { 1556 version (D_Coverage) {} else pragma(inline, true); 1557 import core.internal.traits : hasElaborateCopyConstructor; 1558 import std.traits : isBasicType; 1559 static if (!hasElaborateCopyConstructor!T) { 1560 import std.typecons : Nullable; 1561 static if (is(T == class) || 1562 is(T == string)) 1563 dst = cast(T)src; 1564 else static if (isBasicType!T || is(T == Nullable!(_), _)) // `Nullable` types cannot be emplaced 1565 dst = src; 1566 else { /+ TODO: can this case occur? +/ 1567 import core.internal.traits : Unqual; 1568 import core.lifetime : emplace; 1569 emplace(&dst, cast(Unqual!T)src); 1570 } 1571 } 1572 else static if (__traits(hasMember, T, "dup")) { 1573 import core.lifetime : emplace; 1574 /+ TODO: when `emplace` can handle src being an r-value of uncopyable types replace with: `emplace(&dst, src.dup);` +/ 1575 emplace(&dst); 1576 dst = src.dup; 1577 } 1578 else 1579 debug static assert(0, "cannot duplicate a " ~ T.stringof); 1580 } 1581 1582 /** L-value element reference (and in turn range iterator). 1583 */ 1584 static private struct LvalueElementRef(SomeMap) { 1585 import std.traits : isMutable; 1586 debug static assert(isMutable!SomeMap, "SomeMap type must be mutable"); 1587 1588 private SomeMap* _table; // scoped access 1589 private size_t _binIndex; // index to bin inside `table` 1590 private size_t _hitCounter; // counter over number of elements popped (needed for length) 1591 1592 this(SomeMap* table) @trusted { 1593 version (D_Coverage) {} else pragma(inline, true); 1594 this._table = table; 1595 static if (SomeMap.isBorrowChecked) 1596 debug { _table.incBorrowCount(); } 1597 } 1598 1599 ~this() nothrow @nogc @trusted { 1600 version (D_Coverage) {} else pragma(inline, true); 1601 static if (SomeMap.isBorrowChecked) 1602 debug { _table.decBorrowCount(); } 1603 } 1604 1605 this(this) @trusted { 1606 version (D_Coverage) {} else pragma(inline, true); 1607 static if (SomeMap.isBorrowChecked) 1608 debug { 1609 assert(_table._borrowCount != 0); 1610 _table.incBorrowCount(); 1611 } 1612 } 1613 1614 /// Check if empty. 1615 bool empty() const @property pure nothrow @safe @nogc { 1616 version (D_Coverage) {} else pragma(inline, true); 1617 return _binIndex == _table.binCount; 1618 } 1619 1620 /// Get number of element left to pop. 1621 @property size_t length() const pure nothrow @safe @nogc { 1622 version (D_Coverage) {} else pragma(inline, true); 1623 return _table.length - _hitCounter; 1624 } 1625 1626 @property typeof(this) save() { // ForwardRange 1627 version (D_Coverage) {} else pragma(inline, true); 1628 return this; 1629 } 1630 1631 void popFront() in(!empty) { 1632 version (LDC) pragma(inline, true); 1633 _binIndex += 1; 1634 findNextNonEmptyBin(); 1635 _hitCounter += 1; 1636 } 1637 1638 private void findNextNonEmptyBin() { 1639 version (D_Coverage) {} else pragma(inline, true); 1640 while (_binIndex != (*_table).binCount && 1641 !(*_table).isOccupiedAtIndex(_binIndex)) 1642 _binIndex += 1; 1643 } 1644 } 1645 1646 /** R-value element reference (and in turn range iterator). 1647 * 1648 * Does need to do borrow-checking. 1649 */ 1650 static private struct RvalueElementRef(SomeMap) { 1651 debug import std.traits : isMutable; 1652 debug static assert(isMutable!SomeMap, "SomeMap type must be mutable"); 1653 1654 SomeMap _table; // owned table 1655 size_t _binIndex; // index to bin inside table 1656 size_t _hitCounter; // counter over number of elements popped 1657 1658 /// Check if empty. 1659 bool empty() const @property pure nothrow @safe @nogc { 1660 version (D_Coverage) {} else pragma(inline, true); 1661 return _binIndex == _table.binCount; 1662 } 1663 1664 /// Get number of element left to pop. 1665 @property size_t length() const pure nothrow @safe @nogc { 1666 version (D_Coverage) {} else pragma(inline, true); 1667 return _table.length - _hitCounter; 1668 } 1669 1670 void popFront() in(!empty) { 1671 version (LDC) pragma(inline, true); 1672 _binIndex += 1; 1673 findNextNonEmptyBin(); 1674 _hitCounter += 1; 1675 } 1676 1677 private void findNextNonEmptyBin() { 1678 version (D_Coverage) {} else pragma(inline, true); 1679 while (_binIndex != _table.binCount && 1680 !_table.isOccupiedAtIndex(_binIndex)) 1681 _binIndex += 1; 1682 } 1683 } 1684 1685 /** Hash set with in-place open-addressing, storing keys (elements) of type `K`. 1686 * 1687 * Reuse `HybridHashMap` with its V-type set to `void`. 1688 * 1689 * See_Also: `HybridHashMap`. 1690 */ 1691 alias HybridHashSet(K, 1692 alias hasher = hashOf, 1693 string keyEqualPred = defaultKeyEqualPredOf!K, 1694 Allocator = Mallocator, 1695 Options options = Options.init) = 1696 HybridHashMap!(K, void, hasher, keyEqualPred, Allocator, options); 1697 1698 import std.functional : unaryFun; 1699 1700 /** Remove all elements in `x` matching `pred`. 1701 * 1702 * TODO: make this generic for all iterable containers and move to 1703 * container/common.d. 1704 */ 1705 size_t removeAllMatching(alias pred, SomeMap)(auto ref SomeMap x) @trusted 1706 if (is(SomeMap == HybridHashMap!(_), _...) && /+ TODO: generalize to `isSetOrMap` +/ 1707 is(typeof((unaryFun!pred)))) { 1708 import nxt.nullable_traits : nullify; 1709 size_t removalCount = 0; 1710 foreach (immutable i, ref bin; x._store.elts) { 1711 /+ TODO: +/ 1712 // move to SomeMap.removeRef(bin) // uses: `offset = &bin - _store.elts.ptr` 1713 // move to SomeMap.inplaceRemove(bin) // uses: `offset = &bin - _store.elts.ptr` 1714 // or to SomeMap.removeAtIndex(i) 1715 if (x.isOccupiedAtIndex(i) && 1716 unaryFun!pred(bin)) { 1717 x.tagAsLazilyDeletedElementAtIndex(i); 1718 removalCount += 1; 1719 } 1720 } 1721 x._count = x._count - removalCount; 1722 return removalCount; /+ TODO: remove this return value +/ 1723 } 1724 1725 /** Returns: `x` eagerly filtered on `pred`. 1726 * 1727 * TODO: move to container/common.d with more generic template restrictions 1728 */ 1729 SomeMap filtered(alias pred, SomeMap)(SomeMap x) 1730 if (is(SomeMap == HybridHashMap!(_), _...)) { /+ TODO: generalize to `isSetOrMap` +/ 1731 import core.lifetime : move; 1732 import std.functional : not; 1733 x.removeAllMatching!(not!pred); // `x` is a singleton (r-value) so safe to mutate 1734 return move(x); // functional 1735 } 1736 1737 /** Returns: `x` eagerly intersected with `y`. 1738 * 1739 * TODO: move to container/common.d. 1740 * TODO: Check that `ElementType`'s of `C1` and `C2` match. Look at std.algorithm.intersection for hints. 1741 */ 1742 auto intersectedWith(C1, C2)(C1 x, auto ref C2 y) 1743 if (is(C1 == HybridHashMap!(_1), _1...) && /+ TODO: generalize to `isSetOrMap` +/ 1744 is(C2 == HybridHashMap!(_2), _2...)) /+ TODO: generalize to `isSetOrMap` +/ 1745 { 1746 import core.lifetime : move; 1747 static if (__traits(isRef, y)) // y is l-value 1748 // @("complexity", "O(x.length)") 1749 return move(x).filtered!(_ => y.contains(_)); // only x can be reused 1750 else { 1751 /* both are r-values so reuse the shortest */ 1752 // @("complexity", "O(min(x.length), min(y.length))") 1753 if (x.length < y.length) 1754 return move(x).filtered!(_ => y.contains(_)); // functional 1755 else 1756 return move(y).filtered!(_ => x.contains(_)); // functional 1757 } 1758 } 1759 1760 /// exercise opEquals 1761 pure nothrow @safe @nogc unittest { 1762 import std.typecons : Nullable; 1763 import nxt.digest.fnv : FNV; 1764 1765 alias K = Nullable!(ulong, ulong.max); 1766 alias X = HybridHashSet!(K, FNV!(64, true)); 1767 1768 const n = 100; 1769 1770 X a; 1771 foreach (const i_; 0 .. n) { 1772 const i = 1113*i_; // insert in order 1773 assert(!a.contains(K(i))); 1774 assert(!a.containsUsingLinearSearch(K(i))); 1775 assert(a.insertAndReturnElement(K(i)) == K(i)); 1776 assert(a.contains(K(i))); 1777 assert(a.containsUsingLinearSearch(K(i))); 1778 } 1779 1780 X b; 1781 foreach (const i_; 0 .. n) { 1782 const i = 1113*(n - 1 - i_); // insert in reverse 1783 assert(!b.contains(K(i))); 1784 assert(!b.containsUsingLinearSearch(K(i))); 1785 assert(b.insertAndReturnElement(K(i)) == K(i)); 1786 assert(b.contains(K(i))); 1787 assert(b.containsUsingLinearSearch(K(i))); 1788 } 1789 1790 assert(a == b); 1791 1792 // bin storage must be deterministic 1793 () @trusted { assert(a._store != b._store); }(); 1794 } 1795 1796 pure nothrow @safe @nogc unittest { 1797 import nxt.digest.fnv : FNV; 1798 1799 enum Pot { noun, verb } 1800 struct ExprPot { 1801 string expr; 1802 alias nullifier = expr; 1803 Pot pot; 1804 static immutable nullValue = typeof(this).init; 1805 } 1806 1807 alias X = HybridHashSet!(ExprPot, FNV!(64, true)); 1808 1809 X x; 1810 1811 const aa = "aa"; 1812 1813 // two keys with same contents but different place in memory 1814 const key1 = ExprPot(aa[0 .. 1], Pot.noun); 1815 const key2 = ExprPot(aa[1 .. 2], Pot.noun); 1816 1817 assert(key1 == key2); 1818 assert(key1 !is key2); 1819 1820 assert(!x.contains(key1)); 1821 assert(!x.contains(key2)); 1822 x.insert(key1); 1823 assert(x.contains(key1)); 1824 assert(x.containsUsingLinearSearch(key1)); 1825 assert(x.contains(key2)); 1826 /* assert(x.containsUsingLinearSearch(key2)); */ 1827 assert(key1 in x); 1828 assert(key2 in x); 1829 } 1830 1831 /// `string` as key 1832 pure nothrow @safe @nogc unittest { 1833 import nxt.container.traits : mustAddGCRange; 1834 import nxt.digest.fnv : FNV; 1835 1836 alias X = HybridHashSet!(string, FNV!(64, true)); 1837 debug static assert(!mustAddGCRange!X); 1838 debug static assert(X.sizeof == 24); // dynamic arrays also `hasAddressLikeKey` 1839 1840 auto x = X(); 1841 1842 auto testEscapeShouldFail()() @safe pure { 1843 X x; 1844 x.insert("a"); 1845 return x.byElement; 1846 } 1847 1848 auto testEscapeShouldFailFront()() @safe pure { 1849 X x; 1850 x.insert("a"); 1851 return x.byElement.front; 1852 } 1853 1854 assert(&"a"[0] is &"a"[0]); // string literals are store in common place 1855 1856 const aa = "aa"; 1857 1858 // string slices are equal when elements are equal regardless of position 1859 // (.ptr) in memory 1860 assert(x.insertAndReturnElement(aa[0 .. 1]) !is "a"); 1861 x.insert(aa[0 .. 1]); 1862 assert(x.insertAndReturnElement(aa[0 .. 1]) is aa[0 .. 1]); 1863 assert(x.contains(aa[1 .. 2])); 1864 assert(x.containsUsingLinearSearch(aa[1 .. 2])); 1865 1866 const(char)[] aa_ = "aa"; 1867 assert(x.contains(aa_[1 .. 2])); 1868 assert(x.containsUsingLinearSearch(aa_[1 .. 2])); 1869 assert(aa_[1 .. 2] in x); 1870 1871 char[2] aa__; aa__ = "aa"; 1872 assert(x.contains(aa__[1 .. 2])); 1873 assert(x.containsUsingLinearSearch(aa__[1 .. 2])); 1874 assert(aa__[1 .. 2] in x); 1875 1876 const bb = "bb"; 1877 1878 assert(x.insertAndReturnElement(bb[0 .. 1]) is bb[0 .. 1]); // returns newly added ref 1879 assert(x.insertAndReturnElement(bb[0 .. 1]) !is "b"); // return other ref not equal new literal 1880 x.insert(bb[0 .. 1]); 1881 assert(x.contains(bb[1 .. 2])); 1882 assert(x.containsUsingLinearSearch(bb[1 .. 2])); 1883 1884 x.remove(aa[0 .. 1]); 1885 assert(!x.contains(aa[1 .. 2])); 1886 assert(!x.containsUsingLinearSearch(aa[1 .. 2])); 1887 assert(x.contains(bb[1 .. 2])); 1888 assert(x.containsUsingLinearSearch(bb[1 .. 2])); 1889 1890 x.remove(bb[0 .. 1]); 1891 assert(!x.contains(bb[1 .. 2])); 1892 assert(!x.containsUsingLinearSearch(bb[1 .. 2])); 1893 1894 x.insert("a"); 1895 x.insert("b"); 1896 assert(x.contains("a")); 1897 assert(x.containsUsingLinearSearch("a")); 1898 assert(x.contains("b")); 1899 assert(x.containsUsingLinearSearch("b")); 1900 1901 debug static assert(!__traits(compiles, { testEscapeShouldFail(); } )); 1902 /+ TODO: this should fail: +/ 1903 /+ TODO: debug static assert(!__traits(compiles, { testEscapeShouldFailFront(); } )); +/ 1904 } 1905 1906 /// `string` as key 1907 pure nothrow @safe unittest { 1908 import nxt.digest.fnv : FNV; 1909 alias X = HybridHashSet!(string, FNV!(64, true)); 1910 auto x = X(); 1911 1912 char[2] cc = "cc"; // mutable chars 1913 assert(x.insertAndReturnElement(cc[]) !is cc[]); // will allocate new slice 1914 1915 const cc_ = "cc"; // immutable chars 1916 assert(x.insertAndReturnElement(cc_[]) !is cc[]); // will not allocate 1917 } 1918 1919 /// array container as value type 1920 pure nothrow @safe @nogc unittest { 1921 import std.meta : AliasSeq; 1922 import std.typecons : Nullable; 1923 import nxt.container.traits : mustAddGCRange; 1924 import nxt.digest.fnv : FNV; 1925 import nxt.array_help : s; 1926 1927 alias K = Nullable!(uint, uint.max); 1928 1929 alias VE = Nullable!(uint, uint.max); 1930 alias V = HybridHashSet!(VE, FNV!(64, true)); 1931 1932 debug static assert(!mustAddGCRange!V); 1933 1934 foreach (X; AliasSeq!(HybridHashMap!(K, V, FNV!(64, true)))) { 1935 const VE n = 600; 1936 1937 auto x = X(); 1938 1939 { // scoped range 1940 auto xkeys = x.byKey; 1941 assert(xkeys.length == 0); 1942 foreach (ref key; xkeys) { 1943 debug static assert(is(typeof(key) == const(K))); 1944 assert(0); 1945 } 1946 foreach (ref key; X().byKey) { 1947 debug static assert(is(typeof(key) == const(K))); 1948 assert(0); 1949 } 1950 } 1951 1952 foreach (immutable i; 0 .. n) { 1953 assert(x.length == i); 1954 1955 auto key = K(i); 1956 auto value = V.withElements([VE(i)].s); 1957 1958 x[key] = value.dup; 1959 assert(x.length == i + 1); 1960 assert(x.contains(key)); 1961 /+ TODO: assert(x.containsUsingLinearSearch(key)); +/ 1962 { 1963 auto valuePtr = key in x; 1964 assert(valuePtr); 1965 assert(*valuePtr == value); 1966 } 1967 1968 x.remove(key); 1969 assert(x.length == i); 1970 assert(!x.contains(key)); 1971 assert(key !in x); 1972 1973 x[key] = value.dup; 1974 assert(x.length == i + 1); 1975 assert(x.contains(key)); 1976 { 1977 auto valuePtr = key in x; 1978 assert(valuePtr && *valuePtr == value); 1979 } 1980 } 1981 1982 assert(x is x); 1983 1984 x = x.dup; 1985 1986 auto y = x.dup; 1987 assert(x !is y); 1988 assert(x.length == y.length); 1989 1990 assert(y == x); 1991 assert(x == y); 1992 1993 foreach (ref key; x.byKey) { 1994 assert(x.contains(key)); 1995 } 1996 1997 foreach (ref keyValue; x.byKeyValue) { 1998 assert(x.contains(keyValue.key)); 1999 auto keyValuePtr = keyValue.key in x; 2000 assert(keyValuePtr && 2001 *keyValuePtr == keyValue.value); 2002 } 2003 2004 foreach (immutable i; 0 .. n) { 2005 assert(x.length == n - i); 2006 2007 auto key = K(i); 2008 auto value = V.withElements([VE(i)].s); 2009 2010 assert(x.contains(key)); 2011 { 2012 auto valuePtr = key in x; 2013 assert(valuePtr && *valuePtr == value); 2014 } 2015 2016 x.remove(key); 2017 assert(!x.contains(key)); 2018 assert(key !in x); 2019 } 2020 2021 auto z = y.dup; 2022 assert(y == z); 2023 2024 /* remove all elements in `y` using `removeAllMatching` and all elements 2025 * in `z` using `removeAllMatching` */ 2026 foreach (immutable i; 0 .. n) { 2027 assert(y.length == n - i); 2028 assert(z.length == n - i); 2029 2030 auto key = K(i); 2031 auto value = V.withElements([VE(i)].s); 2032 2033 assert(y.contains(key)); 2034 { 2035 auto valuePtr = key in y; 2036 assert(valuePtr && *valuePtr == value); 2037 } 2038 assert(z.contains(key)); 2039 { 2040 auto valuePtr = key in z; 2041 assert(valuePtr && *valuePtr == value); 2042 } 2043 2044 y.remove(key); 2045 assert(z.removeAllMatching!((in element) => element.key is key) == 1); 2046 assert(y == z); 2047 2048 assert(!y.contains(key)); 2049 assert(!z.contains(key)); 2050 2051 assert(key !in y); 2052 assert(key !in z); 2053 } 2054 } 2055 } 2056 2057 /// r-value and l-value intersection 2058 pure nothrow @safe @nogc unittest { 2059 import core.lifetime : move; 2060 import std.typecons : Nullable; 2061 import nxt.digest.fnv : FNV; 2062 import nxt.array_help : s; 2063 2064 alias K = Nullable!(uint, uint.max); 2065 alias X = HybridHashSet!(K, FNV!(64, true)); 2066 2067 auto x = X(); 2068 2069 { // scoped range 2070 foreach (ref _; x.byElement) { assert(0); } 2071 } 2072 2073 auto x0 = X.init; 2074 assert(x0.length == 0); 2075 assert(x0._store.elts.length == 0); 2076 assert(!x0.contains(K(1))); 2077 2078 auto x1 = X.withElements([K(12)].s); 2079 assert(x1.length == 1); 2080 assert(x1.contains(K(12))); 2081 2082 auto x2 = X.withElements([K(10), K(12)].s); 2083 assert(x2.length == 2); 2084 assert(x2.contains(K(10))); 2085 assert(x2.contains(K(12))); 2086 2087 auto x3 = X.withElements([K(12), K(13), K(14)].s); 2088 assert(x3.length == 3); 2089 assert(x3.contains(K(12))); 2090 assert(x3.contains(K(13))); 2091 assert(x3.contains(K(14))); 2092 2093 auto z = X.withElements([K(10), K(12), K(13), K(15)].s); 2094 assert(z.length == 4); 2095 assert(z.contains(K(10))); 2096 assert(z.contains(K(12))); 2097 assert(z.contains(K(13))); 2098 assert(z.contains(K(15))); 2099 2100 auto y = move(z).intersectedWith(x2); 2101 assert(y.length == 2); 2102 assert(y.contains(K(10))); 2103 assert(y.contains(K(12))); 2104 assert(y.containsUsingLinearSearch(K(10))); 2105 assert(y.containsUsingLinearSearch(K(12))); 2106 } 2107 2108 /// r-value and r-value intersection 2109 pure nothrow @safe @nogc unittest { 2110 import std.typecons : Nullable; 2111 import nxt.digest.fnv : FNV; 2112 import nxt.array_help : s; 2113 2114 alias K = Nullable!(uint, uint.max); 2115 alias X = HybridHashSet!(K, FNV!(64, true)); 2116 2117 auto y = X.withElements([K(10), K(12), K(13), K(15)].s).intersectedWith(X.withElements([K(12), K(13)].s)); 2118 assert(y.length == 2); 2119 assert(y.contains(K(12))); 2120 assert(y.contains(K(13))); 2121 assert(y.containsUsingLinearSearch(K(12))); 2122 assert(y.containsUsingLinearSearch(K(13))); 2123 } 2124 2125 /** Returns: `x` eagerly intersected with `y`. 2126 TODO: move to container/common.d. 2127 */ 2128 auto intersectWith(C1, C2)(ref C1 x, auto ref const(C2) y) 2129 if (is(C1 == HybridHashMap!(_1), _1) && 2130 is(C2 == HybridHashMap!(_2), _2)) { 2131 return x.removeAllMatching!(_ => !y.contains(_)); 2132 } 2133 2134 /// r-value and l-value intersection 2135 pure nothrow @safe @nogc unittest { 2136 import std.typecons : Nullable; 2137 import nxt.digest.fnv : FNV; 2138 import nxt.array_help : s; 2139 2140 alias K = Nullable!(uint, uint.max); 2141 alias X = HybridHashSet!(K, FNV!(64, true)); 2142 2143 auto x = X.withElements([K(12), K(13)].s); 2144 auto y = X.withElements([K(10), K(12), K(13), K(15)].s); 2145 y.intersectWith(x); 2146 assert(y.length == 2); 2147 assert(y.contains(K(12))); 2148 assert(y.containsUsingLinearSearch(K(12))); 2149 assert(y.contains(K(13))); 2150 assert(y.containsUsingLinearSearch(K(13))); 2151 } 2152 2153 /// Range over elements of l-value instance of this. 2154 static struct ByLvalueElement(SomeMap) // public for now because this is needed in `knet.zing.Zing.EdgesOfRels` 2155 { 2156 import nxt.container.traits : isAddress; 2157 pragma(inline, true): 2158 /+ TODO: functionize +/ 2159 static if (isAddress!(SomeMap.ElementType)) // for reference types 2160 { 2161 /// Get reference to front element. 2162 @property scope SomeMap.ElementType front()() return @trusted { 2163 // cast to head-const for class key 2164 return (cast(typeof(return))_table._store.elts[_binIndex]); 2165 } 2166 } 2167 else { 2168 /// Get reference to front element. 2169 @property scope auto ref front() return @trusted { 2170 return *(cast(const(SomeMap.ElementType)*)&_table._store.elts[_binIndex]); // propagate constnes 2171 } 2172 } 2173 import core.internal.traits : Unqual; 2174 // unqual to reduce number of instantations of `LvalueElementRef` 2175 public LvalueElementRef!(Unqual!SomeMap) _elementRef; 2176 alias _elementRef this; 2177 } 2178 2179 /// Range over elements of r-value instance of this. 2180 static private struct ByRvalueElement(SomeMap) { 2181 import nxt.container.traits : isAddress; 2182 this(this) @disable; 2183 pragma(inline, true): 2184 static if (isAddress!(SomeMap.ElementType)) // for reference types 2185 { 2186 /// Get reference to front element. 2187 @property scope SomeMap.ElementType front()() return @trusted { 2188 // cast to head-const for class key 2189 return cast(typeof(return))_table._store.elts[_binIndex]; 2190 } 2191 } else { 2192 /// Get reference to front element. 2193 @property auto ref front() return scope { 2194 return *(cast(const(SomeMap.ElementType)*)&_table._store.elts[_binIndex]); // propagate constnes 2195 } 2196 } 2197 import core.internal.traits : Unqual; 2198 public RvalueElementRef!(Unqual!SomeMap) _elementRef; 2199 alias _elementRef this; 2200 } 2201 2202 /** Returns: range that iterates through the elements of `c` in undefined order. 2203 */ 2204 auto byElement(SomeMap)(auto ref return SomeMap c) @trusted 2205 if (is(SomeMap == HybridHashMap!(_), _...) && 2206 !SomeMap.hasValue) { 2207 import core.internal.traits : Unqual; 2208 alias M = Unqual!SomeMap; 2209 alias C = const(SomeMap); // be const for now 2210 static if (__traits(isRef, c)) // `c` is an l-value 2211 auto result = ByLvalueElement!C((LvalueElementRef!(M)(cast(M*)&c))); 2212 else { // `c` was is an r-value and can be moved 2213 import core.lifetime : move; 2214 auto result = ByRvalueElement!C((RvalueElementRef!(M)(move(*(cast(M*)&c))))); // reinterpret 2215 } 2216 result.findNextNonEmptyBin(); 2217 return result; 2218 } 2219 alias range = byElement; // EMSI-container naming 2220 2221 static private struct ByKey_lvalue(SomeMap) 2222 if (is(SomeMap == HybridHashMap!(_), _...) && 2223 SomeMap.hasValue) { 2224 @property auto ref front() const return scope // key access must be const, TODO: auto ref => ref K 2225 { 2226 version (D_Coverage) {} else pragma(inline, true); 2227 return _table._store.elts[_binIndex].key; 2228 } 2229 import core.internal.traits : Unqual; 2230 public LvalueElementRef!(Unqual!SomeMap) _elementRef; 2231 alias _elementRef this; 2232 } 2233 2234 static private struct ByKey_rvalue(SomeMap) 2235 if (is(SomeMap == HybridHashMap!(_), _...) && 2236 SomeMap.hasValue) { 2237 @property auto ref front() const return scope // key access must be const, TODO: auto ref => ref K 2238 { 2239 version (D_Coverage) {} else pragma(inline, true); 2240 return _table._store.elts[_binIndex].key; 2241 } 2242 import core.internal.traits : Unqual; 2243 public RvalueElementRef!(Unqual!SomeMap) _elementRef; 2244 alias _elementRef this; 2245 } 2246 2247 /** Returns: range that iterates through the keys of `c` in undefined order. 2248 */ 2249 auto byKey(SomeMap)(auto ref /*TODO: return*/ SomeMap c) @trusted 2250 if (is(SomeMap == HybridHashMap!(_), _...) && 2251 SomeMap.hasValue) { 2252 import core.internal.traits : Unqual; 2253 alias M = Unqual!SomeMap; 2254 alias C = const(SomeMap); // be const 2255 static if (__traits(isRef, c)) // `c` is an l-value 2256 auto result = ByKey_lvalue!C((LvalueElementRef!(M)(cast(M*)&c))); 2257 else { // `c` was is an r-value and can be moved 2258 import core.lifetime : move; 2259 auto result = ByKey_rvalue!C((RvalueElementRef!M(move(*(cast(M*)&c))))); // reinterpret 2260 } 2261 result.findNextNonEmptyBin(); 2262 return result; 2263 } 2264 2265 static private struct ByValue_lvalue(SomeMap) 2266 if (is(SomeMap == HybridHashMap!(_), _...) && 2267 SomeMap.hasValue) { 2268 @property scope auto ref front() return @trusted /+ TODO: auto ref => ref V +/ 2269 { 2270 version (D_Coverage) {} else pragma(inline, true); 2271 /+ TODO: functionize +/ 2272 import std.traits : isMutable; 2273 static if (isMutable!(SomeMap)) /+ TODO: can this be solved without this `static if`? +/ 2274 alias E = SomeMap.ValueType; 2275 else 2276 alias E = const(SomeMap.ValueType); 2277 return *(cast(E*)&_table._store.elts[_binIndex].value); 2278 } 2279 import core.internal.traits : Unqual; 2280 public LvalueElementRef!(Unqual!SomeMap) _elementRef; 2281 alias _elementRef this; 2282 } 2283 2284 static private struct ByValue_rvalue(SomeMap) 2285 if (is(SomeMap == HybridHashMap!(_), _...) && 2286 SomeMap.hasValue) { 2287 @property scope auto ref front() return @trusted /+ TODO: auto ref => ref V +/ 2288 { 2289 version (D_Coverage) {} else pragma(inline, true); 2290 /+ TODO: functionize +/ 2291 import std.traits : isMutable; 2292 static if (isMutable!(SomeMap)) /+ TODO: can this be solved without this `static if`? +/ 2293 alias E = SomeMap.ValueType; 2294 else 2295 alias E = const(SomeMap.ValueType); 2296 return *(cast(E*)&_table._store.elts[_binIndex].value); 2297 } 2298 import core.internal.traits : Unqual; 2299 public RvalueElementRef!(Unqual!SomeMap) _elementRef; 2300 alias _elementRef this; 2301 } 2302 2303 /** Returns: range that iterates through the values of `c` in undefined order. 2304 */ 2305 auto byValue(SomeMap)(auto ref return SomeMap c) @trusted 2306 if (is(SomeMap == HybridHashMap!(_), _...) && 2307 SomeMap.hasValue) { 2308 import core.internal.traits : Unqual; 2309 import std.traits : isMutable; 2310 alias M = Unqual!SomeMap; 2311 alias C = const(SomeMap); 2312 static if (__traits(isRef, c)) // `c` is an l-value 2313 auto result = ByValue_lvalue!SomeMap((LvalueElementRef!(M)(cast(M*)&c))); 2314 else // `c` was is an r-value and can be moved 2315 { 2316 import core.lifetime : move; 2317 auto result = ByValue_rvalue!C((RvalueElementRef!M(move(*(cast(M*)&c))))); // reinterpret 2318 } 2319 result.findNextNonEmptyBin(); 2320 return result; 2321 } 2322 2323 static private struct ByKeyValue_lvalue(SomeMap) 2324 if (is(SomeMap == HybridHashMap!(_), _...) && 2325 SomeMap.hasValue) { 2326 @property scope auto ref front() return @trusted /+ TODO: auto ref => ref T +/ 2327 { 2328 version (D_Coverage) {} else pragma(inline, true); 2329 /+ TODO: functionize +/ 2330 import std.traits : isMutable; 2331 static if (isMutable!(SomeMap)) 2332 alias E = SomeMap.KeyValueType; 2333 else 2334 alias E = const(SomeMap.T); 2335 return *(cast(E*)&_table._store.elts[_binIndex]); 2336 } 2337 import core.internal.traits : Unqual; 2338 public LvalueElementRef!(Unqual!SomeMap) _elementRef; 2339 alias _elementRef this; 2340 } 2341 2342 /** Returns: range that iterates through the key-value-pairs of `c` in undefined order. 2343 */ 2344 auto byKeyValue(SomeMap)(auto ref return SomeMap c) @trusted 2345 if (is(SomeMap == HybridHashMap!(_), _...) && 2346 SomeMap.hasValue) { 2347 import core.internal.traits : Unqual; 2348 alias M = Unqual!SomeMap; 2349 static if (__traits(isRef, c)) // `c` is an l-value 2350 auto result = ByKeyValue_lvalue!SomeMap((LvalueElementRef!(M)(cast(M*)&c))); 2351 else // `c` was is an r-value and can be moved 2352 { 2353 import core.lifetime : move; 2354 auto result = ByKeyValue_rvalue!SomeMap((RvalueElementRef!M(move(*(cast(M*)&c))))); // reinterpret 2355 } 2356 result.findNextNonEmptyBin(); 2357 return result; 2358 } 2359 2360 /// make range from l-value and r-value. element access is always const 2361 pure @safe unittest { 2362 import core.exception : AssertError; 2363 import std.typecons : Nullable; 2364 import nxt.digest.fnv : FNV; 2365 import nxt.array_help : s; 2366 debug import std.exception : assertThrown; 2367 2368 import std.algorithm.searching : count; 2369 alias K = Nullable!(uint, uint.max); 2370 alias X = HybridHashSet!(K, FNV!(64, true), defaultKeyEqualPredOf!K, Mallocator, Options(GrowOnlyFlag.no, BorrowCheckFlag.yes)); 2371 2372 auto k11 = K(11); 2373 auto k22 = K(22); 2374 auto k33 = K(33); 2375 auto ks = [k11, k22, k33].s; 2376 auto k44 = K(44); 2377 2378 // mutable 2379 auto x = X.withElements(ks); 2380 assert(!x.contains(k44)); 2381 assert(!x.containsUsingLinearSearch(k44)); 2382 assert(x.length == 3); 2383 2384 assert(x.byElement.count == x.length); 2385 foreach (e; x.byElement) // from l-value 2386 { 2387 debug static assert(is(typeof(e) == const(K))); // always const access 2388 2389 // range invalidation forbidden: 2390 debug 2391 { 2392 assertThrown!AssertError(x.reserveExtra(1)); // range invalidation 2393 assertThrown!AssertError(x.clear()); // range invalidation 2394 assertThrown!AssertError(x.insert(k11)); // range invalidation 2395 assertThrown!AssertError(x.insertN([k11].s)); // range invalidation 2396 assertThrown!AssertError(x.remove(k11)); // range invalidation 2397 } 2398 2399 // allowed 2400 assert(x.contains(e)); 2401 assert(x.containsUsingLinearSearch(e)); 2402 2403 const eHit = e in x; 2404 assert(eHit); // found 2405 assert(*eHit is e); // and the value equals what we searched for 2406 2407 const eDup = x.dup; // duplication is `const` and allowed 2408 assert(eDup == x); 2409 } 2410 2411 // const 2412 const y = X.withElements(ks); 2413 assert(!x.contains(k44)); 2414 assert(!x.containsUsingLinearSearch(k44)); 2415 foreach (e; y.byElement) // from l-value 2416 { 2417 auto _ = y.byElement; // ok to read-borrow again 2418 assert(y.contains(e)); 2419 assert(y.containsUsingLinearSearch(e)); 2420 debug static assert(is(typeof(e) == const(K))); 2421 } 2422 2423 foreach (e; X.withElements([K(11)].s).byElement) // from r-value 2424 { 2425 assert(e == K(11)); 2426 debug static assert(is(typeof(e) == const(K))); // always const access 2427 } 2428 } 2429 2430 /// range checking 2431 pure @safe unittest { 2432 import core.exception : RangeError; 2433 import std.typecons : Nullable; 2434 import nxt.digest.fnv : FNV; 2435 debug import std.exception : assertThrown, assertNotThrown; 2436 immutable n = 11; 2437 2438 alias K = Nullable!(uint, uint.max); 2439 alias V = uint; 2440 2441 alias X = HybridHashMap!(K, V, FNV!(64, true)); 2442 2443 auto s = X.withCapacity(n); 2444 2445 void dummy(ref V value) {} 2446 2447 debug assertThrown!RangeError(dummy(s[K(0)])); 2448 2449 foreach (immutable i; 0 .. n) { 2450 const k = K(i); 2451 s[k] = V(i); 2452 debug assertNotThrown!RangeError(dummy(s[k])); 2453 } 2454 2455 foreach (immutable i; 0 .. n) { 2456 const k = K(i); 2457 assert(s.remove(k)); 2458 debug assertThrown!RangeError(dummy(s[k])); 2459 } 2460 2461 s[K(0)] = V.init; 2462 auto vp = K(0) in s; 2463 debug static assert(is(typeof(vp) == V*)); 2464 assert((*vp) == V.init); 2465 2466 assert(s.remove(K(0))); 2467 assert(K(0) !in s); 2468 2469 X t; 2470 t.reserveExtra(4096); 2471 2472 t.clear(); 2473 } 2474 2475 /// class as value 2476 pure @safe unittest { 2477 import core.exception : RangeError; 2478 import std.typecons : Nullable; 2479 debug import std.exception : assertThrown, assertNotThrown; 2480 import nxt.digest.fnv : FNV; 2481 2482 immutable n = 11; 2483 2484 alias K = Nullable!(uint, uint.max); 2485 class V 2486 { 2487 this(uint data) { this.data = data; } 2488 uint data; 2489 } 2490 2491 alias X = HybridHashMap!(K, V, FNV!(64, true)); 2492 2493 auto s = X.withCapacity(n); 2494 2495 void dummy(ref V value) {} 2496 2497 debug assertThrown!RangeError(dummy(s[K(0)])); 2498 2499 foreach (immutable i; 0 .. n) { 2500 const k = K(i); 2501 s[k] = new V(i); 2502 debug assertNotThrown!RangeError(dummy(s[k])); 2503 } 2504 2505 // test range 2506 { 2507 auto sr = s.byKeyValue; // scoped range 2508 assert(sr.length == n); 2509 foreach (immutable i; 0 .. n) { 2510 sr.popFront(); 2511 assert(sr.length == n - i - 1); 2512 } 2513 } 2514 2515 foreach (immutable i; 0 .. n) { 2516 const k = K(i); 2517 assert(s.remove(k)); 2518 debug assertThrown!RangeError(dummy(s[k])); 2519 } 2520 2521 s[K(0)] = V.init; 2522 auto vp = K(0) in s; 2523 debug static assert(is(typeof(vp) == V*)); 2524 2525 assert(s.remove(K(0))); 2526 assert(K(0) !in s); 2527 2528 X t; 2529 t.reserveExtra(4096); 2530 } 2531 2532 /// constness inference of ranges 2533 pure nothrow unittest { 2534 import std.typecons : Nullable; 2535 import nxt.digest.fnv : FNV; 2536 2537 alias K = Nullable!(uint, uint.max); 2538 class V 2539 { 2540 this(uint data) { this.data = data; } 2541 uint data; 2542 } 2543 2544 alias X = HybridHashMap!(K, V, FNV!(64, true)); 2545 const x = X(); 2546 2547 foreach (const e; x.byKey) { 2548 debug static assert(is(typeof(e) == const(X.KeyType))); 2549 } 2550 2551 foreach (const e; x.byValue) { 2552 debug static assert(is(typeof(e) == const(X.ValueType))); 2553 } 2554 2555 foreach (const e; X.init.byValue) { 2556 debug static assert(is(typeof(e) == const(X.ValueType))); 2557 } 2558 2559 foreach (const e; x.byKeyValue) { 2560 debug static assert(is(typeof(e.key) == const(X.KeyType))); 2561 debug static assert(is(typeof(e.value) == const(X.ValueType))); 2562 debug static assert(is(typeof(e) == const(X.ElementType))); 2563 } 2564 } 2565 2566 /// range key constness and value mutability with `class` value 2567 pure nothrow unittest { 2568 import std.typecons : Nullable; 2569 import nxt.digest.fnv : FNV; 2570 2571 struct S 2572 { 2573 uint value; 2574 } 2575 alias K = Nullable!(S, S(uint.min)); // use uint.min to trigger use of faster `allocator.allocateZeroed` 2576 2577 class V 2578 { 2579 this(uint data) { this.data = data; } 2580 uint data; 2581 } 2582 2583 alias X = HybridHashMap!(K, V, FNV!(64, true)); 2584 auto x = X(); 2585 2586 x[K(S(42))] = new V(43); 2587 2588 assert(x.length == 1); 2589 2590 foreach (e; x.byValue) // `e` is auto ref 2591 { 2592 debug static assert(is(typeof(e) == X.ValueType)); // mutable access to value 2593 assert(e.data == 43); 2594 2595 // value mutation side effects 2596 e.data += 1; 2597 assert(e.data == 44); 2598 e.data -= 1; 2599 assert(e.data == 43); 2600 } 2601 2602 foreach (ref e; x.byKeyValue) // `e` is auto ref 2603 { 2604 debug static assert(is(typeof(e.key) == const(X.KeyType))); // const access to key 2605 debug static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value 2606 2607 assert(e.key.value == 42); 2608 assert(e.value.data == 43); 2609 2610 // key cannot be mutated 2611 debug static assert(!__traits(compiles, { e.key.value += 1; })); 2612 2613 // value mutation side effects 2614 e.value.data += 1; 2615 assert(e.value.data == 44); 2616 e.value.data -= 1; 2617 assert(e.value.data == 43); 2618 } 2619 } 2620 2621 /// range key constness and value mutability with `class` key and `class` value 2622 pure nothrow unittest { 2623 import nxt.digest.fnv : FNV; 2624 2625 class K 2626 { 2627 this(uint value) { 2628 this.value = value; 2629 } 2630 2631 @property bool opEquals(in typeof(this) rhs) const 2632 { 2633 return value == rhs.value; 2634 } 2635 2636 uint value; 2637 } 2638 2639 class V 2640 { 2641 this(uint data) { this.data = data; } 2642 uint data; 2643 } 2644 2645 alias X = HybridHashMap!(K, V, FNV!(64, true)); 2646 auto x = X(); 2647 2648 x[new K(42)] = new V(43); 2649 2650 assert(x.length == 1); 2651 2652 foreach (e; x.byValue) // `e` is auto ref 2653 { 2654 debug static assert(is(typeof(e) == X.ValueType)); // mutable access to value 2655 assert(e.data == 43); 2656 2657 // value mutation side effects 2658 e.data += 1; 2659 assert(e.data == 44); 2660 e.data -= 1; 2661 assert(e.data == 43); 2662 } 2663 2664 foreach (ref e; x.byKeyValue) // `e` is auto ref 2665 { 2666 debug static assert(is(typeof(e.key) == X.KeyType)); // mutable access to class key 2667 debug static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value 2668 2669 assert(e.key.value == 42); 2670 assert(e.value.data == 43); 2671 2672 // class key itself should not be mutable 2673 debug static assert(!__traits(compiles, { e.key = null; })); 2674 2675 // members of key can be mutated 2676 debug static assert(__traits(compiles, { e.key.value += 1; })); 2677 2678 // value mutation side effects 2679 e.value.data += 1; 2680 assert(e.value.data == 44); 2681 e.value.data -= 1; 2682 assert(e.value.data == 43); 2683 } 2684 } 2685 2686 /// range key constness and value mutability with `class` key and `class` value 2687 pure nothrow unittest { 2688 import nxt.digest.fnv : FNV; 2689 class K 2690 { 2691 this(uint value) scope { 2692 this.value = value; 2693 } 2694 uint value; 2695 } 2696 2697 struct V 2698 { 2699 this(uint data) { this.data = data; } 2700 this(this) @disable; 2701 uint data; 2702 } 2703 2704 alias X = HybridHashMap!(K, V, FNV!(64, true)); 2705 auto x = X(); 2706 2707 scope key42 = new K(42); 2708 () @trusted { x[key42] = V(43); }(); // TODO: qualify `HybridHashMap.opIndexAssign` with @trusted and remove 2709 2710 assert(x.length == 1); 2711 2712 foreach (ref e; x.byValue) // `e` is auto ref 2713 { 2714 debug static assert(is(typeof(e) == X.ValueType)); // mutable access to value 2715 assert(e.data == 43); 2716 2717 // value mutation side effects 2718 e.data += 1; 2719 assert(e.data == 44); 2720 e.data -= 1; 2721 assert(e.data == 43); 2722 } 2723 2724 foreach (ref e; x.byKeyValue) // `e` is auto ref 2725 { 2726 debug static assert(is(typeof(e.key) == X.KeyType)); // mutable access to class key 2727 debug static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value 2728 2729 assert(e.key.value == 42); 2730 assert(e.value.data == 43); 2731 2732 // value mutation side effects 2733 e.value.data += 1; 2734 assert(e.value.data == 44); 2735 e.value.data -= 1; 2736 assert(e.value.data == 43); 2737 } 2738 2739 assert(x.length == 1); 2740 2741 assert(x.remove(key42)); 2742 assert(x.length == 0); 2743 2744 () @trusted { x[key42] = V(43); }(); // TODO: qualify `HybridHashMap.opIndexAssign` with @trusted and remove 2745 assert(x.length == 1); 2746 } 2747 2748 version (unittest) { 2749 T make(T)(ulong value) { 2750 static if (is(T == class)) 2751 return new T(value); 2752 else 2753 return T(value); 2754 } 2755 } 2756 2757 /// test various things 2758 @trusted unittest { 2759 import std.meta : AliasSeq; 2760 import std.typecons : Nullable; 2761 import std.algorithm.comparison : equal; 2762 import nxt.container.traits : mustAddGCRange; 2763 import nxt.digest.fnv : FNV; 2764 import nxt.array_help : s; 2765 2766 const n = 100; 2767 2768 void testEmptyAll(K, V, X)(ref X x, size_t n, 2769 scope K[] keys) { 2770 assert(x.length == n); 2771 foreach (key; keys) { 2772 static if (X.hasValue) 2773 const element = X.ElementType(key, V.init); 2774 else 2775 alias element = key; 2776 2777 assert(x.length == n - key.get); 2778 2779 const hitPtr = key in x; 2780 static if (X.hasValue) 2781 assert(hitPtr && *hitPtr is element.value); 2782 else 2783 assert(hitPtr && *hitPtr is element); 2784 2785 assert(x.remove(key)); 2786 assert(x.length == n - key.get - 1); 2787 2788 static if (!X.hasValue) { 2789 assert(!x.contains(key)); 2790 assert(!x.containsUsingLinearSearch(key)); 2791 } 2792 assert(key !in x); 2793 assert(!x.remove(key)); 2794 assert(x.length == n - key.get - 1); 2795 } 2796 2797 assert(x.length == 0); 2798 2799 x.clear(); 2800 assert(x.length == 0); 2801 } 2802 2803 X testDup(X)(scope ref X x, size_t n) { 2804 typeof(return) y = x.dup; 2805 2806 assert(x._store.elts.ptr !is y._store.elts.ptr); 2807 assert(x.length == y.length); 2808 assert(y.length == n); 2809 // non-symmetric algorithm so both are needed 2810 assert(y == x); 2811 assert(x == y); 2812 2813 static if (X.hasValue) { 2814 assert(equal(x.byKey, 2815 y.byKey)); 2816 assert(equal(x.byValue, 2817 y.byValue)); 2818 auto a = x.byKeyValue; 2819 auto b = y.byKeyValue; 2820 size_t i = 0; 2821 while (!a.empty && 2822 !b.empty) { 2823 a.popFront(); 2824 b.popFront(); 2825 i++; 2826 } 2827 auto xR = x.byKeyValue; 2828 auto yR = y.byKeyValue; 2829 assert(xR.length == yR.length); 2830 size_t ix = 0; 2831 while (!xR.empty && 2832 !yR.empty) { 2833 auto xK = xR.front.key; 2834 auto yK = yR.front.key; 2835 auto xV = xR.front.value; 2836 auto yV = yR.front.value; 2837 // import std.stdio : writeln; 2838 // writeln("ix:", ix, " xV:", xV, " yV:", yV); 2839 assert(xK == yK); 2840 assert(xV == yV); 2841 assert(xR.front == yR.front); 2842 xR.popFront(); 2843 yR.popFront(); 2844 ix++; 2845 } 2846 assert(equal(x.byKeyValue, 2847 y.byKeyValue)); 2848 } 2849 else 2850 assert(equal(x.byElement, 2851 y.byElement)); 2852 2853 debug static assert(!__traits(compiles, { const _ = x < y; })); // no ordering 2854 2855 return y; 2856 } 2857 2858 alias NullableUlong = Nullable!(ulong, ulong.max); 2859 2860 static class SomeSimpleClass 2861 { 2862 pure nothrow @safe @nogc 2863 this(ulong value) { 2864 this._value = value; 2865 } 2866 2867 pure nothrow @safe @nogc 2868 ulong get() const => _value; 2869 2870 void toString(Sink)(ref scope Sink sink) const { 2871 import std.format : formattedWrite; 2872 sink.formattedWrite(typeof(this).stringof, "(%s)", _value); 2873 } 2874 2875 @property bool opEquals(in typeof(this) rhs) => _value == rhs._value; 2876 2877 private ulong _value; 2878 } 2879 2880 debug static assert(mustAddGCRange!string); 2881 2882 foreach (K; AliasSeq!(SomeSimpleClass, 2883 NullableUlong)) { 2884 foreach (V; AliasSeq!(string, int, void)) { 2885 alias X = HybridHashMap!(K, V, FNV!(64, true)); 2886 2887 static if (!X.hasValue) { 2888 auto k11 = make!K(11); 2889 auto k12 = make!K(12); 2890 auto k13 = make!K(13); 2891 2892 auto x = X.withElements([k11, k12, k13].s); 2893 2894 import std.algorithm : count; 2895 2896 // ByLvalueElement 2897 auto xr = x.byElement; 2898 2899 alias R = typeof(xr); 2900 import std.range.primitives : isInputRange; 2901 import std.traits : ReturnType; 2902 debug static assert(is(typeof(R.init) == R)); 2903 debug static assert(is(ReturnType!((R xr) => xr.empty) == bool)); 2904 2905 /+ TODO: Is this needed? debug static assert(!__traits(compiles, { xr.front == K.init; })); // always head-const +/ 2906 auto f = xr.front; 2907 static if (is(K == class)) { 2908 debug static assert(is(typeof(f) == K)); // tail-mutable 2909 } 2910 else 2911 { 2912 debug static assert(is(typeof(f) == const(K))); // tail-const 2913 } 2914 2915 debug static assert(is(typeof((R xr) => xr.front))); 2916 debug static assert(!is(ReturnType!((R xr) => xr.front) == void)); 2917 debug static assert(is(typeof((R xr) => xr.popFront))); 2918 2919 debug static assert(isInputRange!(typeof(xr))); 2920 2921 assert(x.byElement.count == 3); 2922 2923 X y; 2924 size_t ix = 0; 2925 foreach (ref e; x.byElement) { 2926 assert(x.contains(e)); 2927 assert(x.containsUsingLinearSearch(e)); 2928 assert(!y.contains(e)); 2929 assert(!y.containsUsingLinearSearch(e)); 2930 static if (is(K == class)) 2931 y.insert(cast(K)e); // ugly but ok in tests 2932 else 2933 y.insert(e); 2934 assert(y.contains(e)); 2935 assert(y.containsUsingLinearSearch(e)); 2936 ix++; 2937 } 2938 2939 assert(y.byElement.count == 3); 2940 assert(x == y); 2941 2942 const z = X(); 2943 assert(z.byElement.count == 0); 2944 2945 immutable w = X(); 2946 assert(w.byElement.count == 0); 2947 2948 { 2949 auto xc = X.withElements([k11, k12, k13].s); 2950 assert(xc.length == 3); 2951 assert(xc.contains(k11)); 2952 assert(xc.containsUsingLinearSearch(k11)); 2953 2954 /+ TODO: http://forum.dlang.org/post/kvwrktmameivubnaifdx@forum.dlang.org +/ 2955 xc.removeAllMatching!(_ => _ == k11); 2956 2957 assert(xc.length == 2); 2958 assert(!xc.contains(k11)); 2959 assert(!xc.containsUsingLinearSearch(k11)); 2960 2961 xc.removeAllMatching!(_ => _ == k12); 2962 assert(!xc.contains(k12)); 2963 assert(!xc.containsUsingLinearSearch(k12)); 2964 assert(xc.length == 1); 2965 2966 xc.removeAllMatching!(_ => _ == k13); 2967 assert(!xc.contains(k13)); 2968 assert(!xc.containsUsingLinearSearch(k13)); 2969 assert(xc.length == 0); 2970 2971 // this is ok 2972 foreach (_; xc.byElement) {} 2973 } 2974 2975 { // ByRvalueElement 2976 auto k = X.withElements([k11, k12].s).filtered!(_ => _ != k11).byElement; 2977 debug static assert(isInputRange!(typeof(k))); 2978 assert(k.front == k12); 2979 2980 debug static assert(!__traits(compiles, { k.front = K.init; })); // head-const 2981 static if (is(K == class)) { 2982 debug static assert(is(typeof(k.front) == K)); // tail-mutable 2983 } 2984 else 2985 { 2986 debug static assert(is(typeof(k.front) == const(K))); // tail-const 2987 } 2988 2989 k.popFront(); 2990 assert(k.empty); 2991 } 2992 2993 { 2994 X q; 2995 auto qv = [make!K(11U), make!K(12U), make!K(13U), make!K(14U)].s; 2996 q.insertN(qv[]); 2997 foreach (e; qv[]) { 2998 assert(q.contains(e)); 2999 assert(q.containsUsingLinearSearch(e)); 3000 } 3001 q.clear(); 3002 assert(q.empty); 3003 } 3004 } 3005 3006 static if (is(V == string)) { 3007 debug static assert(mustAddGCRange!V); 3008 debug static assert(mustAddGCRange!(V[1])); 3009 debug static assert(mustAddGCRange!(X.T)); 3010 } 3011 3012 auto x1 = X(); // start empty 3013 3014 // fill x1 3015 3016 import std.array : Appender; 3017 Appender!(K[]) keys; 3018 3019 foreach (immutable key_; 0 .. n) { 3020 auto key = make!K(key_); 3021 keys.put(key); 3022 3023 // create elements 3024 static if (X.hasValue) { 3025 auto value = V.init; 3026 auto element = X.ElementType(key, value); 3027 } 3028 else 3029 // no assignment because Nullable.opAssign may leave rhs in null state 3030 auto element = key; 3031 3032 assert(key !in x1); 3033 3034 assert(x1.length == key.get); 3035 assert(x1.insert(element) == X.InsertionStatus.added); 3036 assert(x1.length == key.get + 1); 3037 3038 static if (X.hasValue) { 3039 import std.conv : to; 3040 auto e2 = X.ElementType(key, (42 + key_).to!V); 3041 assert(x1.insert(e2) == X.InsertionStatus.modified); 3042 assert(x1.contains(key)); 3043 assert(x1.get(key, V.init) == (42 + key_).to!V); 3044 3045 assert(x1.remove(key)); 3046 assert(!x1.contains(key)); 3047 3048 x1[key] = value; // restore value 3049 assert(x1.contains(key)); 3050 } 3051 3052 assert(x1.length == key.get + 1); 3053 3054 const hitPtr = key in x1; 3055 static if (X.hasValue) 3056 assert(hitPtr && *hitPtr == value); 3057 else 3058 assert(hitPtr && *hitPtr is key); 3059 3060 auto status = x1.insert(element); 3061 assert(status == X.InsertionStatus.unmodified); 3062 static if (X.hasValue) 3063 assert(x1.insert(key, value) == X.InsertionStatus.unmodified); 3064 assert(x1.length == key.get + 1); 3065 3066 assert(key in x1); 3067 } 3068 3069 static if (X.hasValue) { 3070 import nxt.container.dynamic_array : Array = DynamicArray; 3071 Array!(X.ElementType) a1; // remember the keys 3072 3073 foreach (const ref key; x1.byKey) { 3074 auto keyPtr = key in x1; 3075 assert(keyPtr); 3076 a1 ~= X.ElementType(cast(K)key, (*keyPtr)); 3077 } 3078 3079 assert(x1.length == a1.length); 3080 3081 foreach (ae; a1[]) { 3082 auto keyPtr = ae.key in x1; 3083 assert(keyPtr); 3084 assert((*keyPtr) is ae.value); 3085 } 3086 } 3087 3088 assert(x1.length == n); 3089 3090 auto x2 = testDup(x1, n); 3091 3092 testEmptyAll!(K, V)(x1, n, keys.data); 3093 3094 testEmptyAll!(K, V)(x2, n, keys.data); // should be not affected by emptying of x1 3095 } 3096 } 3097 } 3098 3099 /// 3100 pure nothrow @safe @nogc unittest { 3101 import std.typecons : Nullable; 3102 import nxt.digest.fnv : FNV; 3103 3104 alias X = HybridHashMap!(Nullable!(size_t, size_t.max), size_t, FNV!(64, true)); 3105 X x; 3106 assert(x.empty); 3107 // import nxt.container.dynamic_array : Array = DynamicArray; 3108 /+ TODO: these segfault: +/ 3109 /+ TODO: auto a = Array!(X.KeyType).withElementsOfRange_untested(x.byKey); // l-value byKey +/ 3110 /+ TODO: auto b = Array!(X.KeyType).withElementsOfRange_untested(X().byKey); // r-value byKey +/ 3111 } 3112 3113 /// manual Nullable type 3114 pure @safe unittest { 3115 import nxt.nullable_traits : isNullable; 3116 import nxt.digest.fnv : FNV; 3117 3118 static class Zing { 3119 pure nothrow @safe @nogc: 3120 this(ulong value) { this._value = value; } 3121 private ulong _value; 3122 } 3123 debug static assert(isNullable!Zing); 3124 3125 enum Alt { unknown, a, b, c, d } 3126 3127 struct ZingRelation { 3128 Zing zing; 3129 Alt alts; 3130 3131 alias nullifier = zing; 3132 static immutable nullValue = typeof(this).init; 3133 3134 bool opEquals(in typeof(this) that) const pure nothrow @safe @nogc 3135 => (this.zing is that.zing && this.alts == that.alts); 3136 } 3137 debug static assert(isNullable!ZingRelation); 3138 3139 alias X = HybridHashSet!(ZingRelation, FNV!(64, true)); 3140 debug static assert(X.sizeof == 24); 3141 X x; 3142 3143 scope e = ZingRelation(new Zing(42), Alt.init); 3144 3145 assert(!x.contains(e)); 3146 assert(!x.containsUsingLinearSearch(e)); 3147 assert(x.insert(e) == X.InsertionStatus.added); 3148 assert(x.contains(e)); 3149 assert(x.containsUsingLinearSearch(e)); 3150 } 3151 3152 /// abstract class value type 3153 @safe unittest { 3154 static abstract class Zing { 3155 pure nothrow @safe @nogc: 3156 } 3157 static class Node : Zing { 3158 pure nothrow @safe @nogc: 3159 } 3160 3161 alias X = HybridHashSet!(Zing); 3162 X x; 3163 3164 const Zing cz = new Node(); 3165 x.insert(cz); // ok to insert const 3166 3167 Zing z = new Node(); 3168 x.insert(z); // ok to insert mutable because hashing is on address by default 3169 } 3170 3171 /// class type with default hashing 3172 @safe unittest { 3173 static class Base { 3174 static size_t dtorCount = 0; // number of calls to this destructor 3175 @safe nothrow @nogc: 3176 ~this() nothrow @nogc { dtorCount += 1; } 3177 pure: 3178 this(ulong value) { this._value = value; } 3179 @property bool opEquals(in typeof(this) rhs) const => _value == rhs._value; 3180 override hash_t toHash() const => hashOf(_value); 3181 private ulong _value; 3182 } 3183 3184 /** Node containing same data members but different type. */ 3185 static class Node : Base { 3186 pure nothrow @safe @nogc: 3187 this(ulong value) { super(value); } 3188 } 3189 debug static assert(is(Node : Base)); 3190 3191 import nxt.hash_functions : hashOfPolymorphic; // neede to separate hash of `Base(N)` from `Node(N)` 3192 alias X = HybridHashSet!(Base, hashOfPolymorphic, "a && b && (typeid(a) is typeid(b)) && a.opEquals(b)"); 3193 debug static assert(X.sizeof == 24); 3194 X x; 3195 3196 // top-class 3197 auto b42 = new Base(42); /+ TODO: qualify as scope when hashOf parameter arg is scope +/ 3198 assert(!x.contains(b42)); 3199 assert(!x.containsUsingLinearSearch(b42)); 3200 assert(x.insert(b42) == X.InsertionStatus.added); 3201 assert(x.contains(b42)); 3202 assert(x.containsUsingLinearSearch(b42)); 3203 assert(x.tryGetElementFromCtorParams!Base(42) !is null); 3204 assert(Base.dtorCount == 1); 3205 assert(x.tryGetElementFromCtorParams!Base(42)._value == 42); 3206 assert(Base.dtorCount == 2); 3207 assert(x.tryGetElementFromCtorParams!Base(41) is null); 3208 assert(Base.dtorCount == 3); 3209 3210 // top-class 3211 auto b43 = new Base(43); /+ TODO: qualify as scope when hashOf parameter arg is scope +/ 3212 assert(!x.contains(b43)); 3213 assert(!x.containsUsingLinearSearch(b43)); 3214 assert(x.insert(b43) == X.InsertionStatus.added); 3215 assert(x.contains(b43)); 3216 assert(x.containsUsingLinearSearch(b43)); 3217 assert(x.tryGetElementFromCtorParams!Base(43) !is null); 3218 assert(Base.dtorCount == 4); 3219 assert(x.tryGetElementFromCtorParams!Base(43)._value == 43); 3220 assert(Base.dtorCount == 5); 3221 3222 // sub-class 3223 assert(x.tryGetElementFromCtorParams!Node(42) is null); 3224 assert(Base.dtorCount == 6); 3225 immutable n42 = new Node(42); 3226 assert(!x.contains(n42)); // mustn't equal to `b42` 3227 assert(!x.containsUsingLinearSearch(n42)); // mustn't equal to `b42` 3228 assert(x.insert(n42) == X.InsertionStatus.added); // added as separate type 3229 assert(x.contains(n42)); 3230 assert(x.containsUsingLinearSearch(n42)); 3231 assert(x.tryGetElementFromCtorParams!Node(42) !is null); 3232 assert(Base.dtorCount == 7); 3233 assert(x.tryGetElementFromCtorParams!Node(42)._value == 42); 3234 assert(Base.dtorCount == 8); 3235 3236 assert(hashOf(b42) == hashOf(n42)); 3237 3238 // sub-class 3239 assert(x.tryGetElementFromCtorParams!Node(43) is null); 3240 assert(Base.dtorCount == 9); 3241 auto n43 = new Node(43); 3242 assert(!x.contains(n43)); // mustn't equal to `b43` 3243 assert(!x.containsUsingLinearSearch(n43)); // mustn't equal to `b43` 3244 assert(x.insert(n43) == X.InsertionStatus.added); // added as separate type 3245 assert(x.contains(n43)); 3246 assert(x.containsUsingLinearSearch(n43)); 3247 assert(x.tryGetElementFromCtorParams!Node(43) !is null); 3248 assert(Base.dtorCount == 10); 3249 assert(x.tryGetElementFromCtorParams!Node(43)._value == 43); 3250 assert(Base.dtorCount == 11); 3251 3252 assert(hashOf(b43) == hashOf(n43)); 3253 } 3254 3255 /// enumeration key 3256 pure @safe unittest { 3257 import nxt.digest.fnv : FNV; 3258 3259 enum Alt { 3260 nullValue, // trait 3261 a, b, c, d 3262 } 3263 alias X = HybridHashSet!(Alt, FNV!(64, true)); 3264 X x; 3265 assert(!x.contains(Alt.a)); 3266 3267 assert(x.insert(Alt.a) == X.InsertionStatus.added); 3268 3269 assert(x.contains(Alt.a)); 3270 assert(x.containsUsingLinearSearch(Alt.a)); 3271 assert(!x.contains(Alt.b)); 3272 assert(!x.contains(Alt.c)); 3273 assert(!x.contains(Alt.d)); 3274 assert(!x.containsUsingLinearSearch(Alt.b)); 3275 assert(!x.containsUsingLinearSearch(Alt.c)); 3276 assert(!x.containsUsingLinearSearch(Alt.d)); 3277 3278 assert(x.remove(Alt.a)); 3279 assert(!x.contains(Alt.a)); 3280 assert(!x.containsUsingLinearSearch(Alt.a)); 3281 } 3282 3283 /// 3284 pure nothrow @safe unittest { 3285 import nxt.digest.fnv : FNV; 3286 static struct Rel { 3287 static immutable nullValue = typeof(this).init; 3288 string name; // relation name. WARNING compiler crashes when qualified with `package` 3289 } 3290 alias X = HybridHashSet!(Rel, FNV!(64, true)); 3291 X x; 3292 foreach (const i; 0 .. 100) { 3293 const char[1] ch = ['a' + i]; 3294 assert(!x.contains(Rel(ch.idup))); 3295 assert(!x.containsUsingLinearSearch(Rel(ch.idup))); 3296 x.insert(Rel(ch.idup)); 3297 assert(x.contains(Rel(ch.idup))); 3298 /* TODO: assert(x.containsUsingLinearSearch(Rel(ch.idup))); */ 3299 } 3300 } 3301 3302 /// `SSOString` as set key type 3303 pure nothrow @safe @nogc unittest { 3304 import nxt.sso_string : SSOString; 3305 import nxt.digest.fnv : FNV; 3306 3307 alias K = SSOString; 3308 static assert(isHoleable!K); 3309 alias X = HybridHashSet!(K, FNV!(64, true)); 3310 const n = 100; 3311 3312 X a; 3313 foreach (const i; 0 .. n) { 3314 const char[1] ch = ['a' + i]; 3315 const k = K(ch); // @nogc 3316 3317 assert(!a.contains(k)); 3318 assert(!a.containsUsingLinearSearch(k)); 3319 3320 assert(a.insert(K(ch)) == X.InsertionStatus.added); 3321 /+ TODO: assert(a.insertAndReturnElement(K(ch)) == k); +/ 3322 assert(a.contains(k)); 3323 assert(a.containsUsingLinearSearch(k)); 3324 3325 assert(a.remove(k)); 3326 assert(!a.contains(k)); 3327 assert(a.insert(K(ch)) == X.InsertionStatus.added); 3328 3329 assert(a.remove(ch[])); 3330 assert(!a.contains(k)); 3331 assert(a.insert(K(ch)) == X.InsertionStatus.added); 3332 } 3333 3334 X b; 3335 foreach (const i; 0 .. n) { 3336 const char[1] ch = ['a' + (n - 1 - i)]; 3337 const k = K(ch); // @nogc 3338 3339 assert(!b.contains(k)); 3340 assert(!b.containsUsingLinearSearch(k)); 3341 3342 assert(b.insert(K(ch)) == X.InsertionStatus.added); 3343 /+ TODO: assert(b.insertAndReturnElement(K(ch)) == k); +/ 3344 3345 assert(b.contains(k)); 3346 assert(b.containsUsingLinearSearch(k)); 3347 3348 assert(b.remove(k)); 3349 assert(!b.contains(k)); 3350 3351 assert(b.insert(K(ch)) == X.InsertionStatus.added); 3352 } 3353 3354 assert(a == b); 3355 3356 const us = K("_"); 3357 assert(!a.contains(us)); 3358 a ~= us; 3359 assert(a.contains(us)); 3360 } 3361 3362 /// test `opIndexOpAssign` 3363 pure nothrow @safe unittest { 3364 import nxt.sso_string : SSOString; 3365 import nxt.digest.fnv : FNV; 3366 3367 alias K = SSOString; 3368 alias V = long; 3369 alias X = HybridHashMap!(K, V, FNV!(64, true)); 3370 3371 X x; 3372 3373 const a = K("a"); 3374 const b = K("b"); 3375 3376 x[a] = 17; 3377 assert(x[a] == 17); 3378 3379 x[a] += 10; // opIndexOpAssign!("+=") with existing key 3380 assert(x[a] == 27); 3381 3382 x[b] += 10; // opIndexOpAssign!("+=") with non-existing key 3383 assert(x[b] == 10); 3384 3385 x[b] *= 10; // opIndexOpAssign!("*=") with non-existing key 3386 assert(x[b] == 100); 3387 3388 assert(x.length == 2); 3389 3390 assert(x.contains(a)); 3391 assert(x.contains(a[])); 3392 () @trusted { assert(a in x); }(); /+ TODO: remove wrapper lambda +/ 3393 assert(a[] in x); 3394 3395 assert(x.contains(b)); 3396 assert(x.contains(b[])); 3397 () @trusted { assert(b in x); }(); /+ TODO: remove wrapper lambda +/ 3398 assert(b[] in x); 3399 3400 const c = K("c"); 3401 assert(!x.contains(c)); 3402 assert(!x.contains(c[])); 3403 assert(c !in x); 3404 assert(c[] !in x); 3405 } 3406 3407 /// use prime numbers as capacity 3408 version (none) /+ TODO: enable +/ 3409 pure @safe unittest { 3410 import nxt.address : AlignedAddress; 3411 alias K = AlignedAddress!1; 3412 alias V = size_t; 3413 alias M = HybridHashMap!(K, V, hashOf, defaultKeyEqualPredOf!K, Mallocator, 3414 BorrowCheckFlag.no, true, UsePrimeCapacityFlag.yes); 3415 M x; 3416 assert(x.empty); 3417 } 3418 3419 /// `SSOString` as map key type 3420 pure nothrow @safe @nogc unittest { 3421 import nxt.sso_string : SSOString; 3422 import nxt.digest.fnv : FNV; 3423 alias K = SSOString; 3424 alias V = long; 3425 alias X = HybridHashMap!(K, V, FNV!(64, true)); 3426 const n = 100; 3427 3428 immutable default_k = K("miss"); 3429 3430 X a; 3431 3432 // insert all 3433 foreach (const i; 0 .. n) { 3434 const char[1] ch = ['a' + i]; 3435 const k = K(ch); // @nogc 3436 assert(k[] == ch[]); 3437 3438 assert(!a.contains(k)); 3439 assert(!a.contains(ch[])); // @nogc 3440 assert(a.getKeyRef(k, default_k)[] is default_k[]); // on miss use `default_k` 3441 /+ TODO: assert(a.getKeyRef(ch, default_k)[] is default_k[]); // on miss use `default_k` +/ 3442 3443 a[k] = V.init; 3444 3445 assert(a.contains(k)); 3446 assert(a.contains(ch[])); // @nogc 3447 assert(a.getKeyRef(k, default_k)[] !is k[]); // on hit doesn't use `default_k` 3448 assert(a.getKeyRef(k, default_k)[] == ch); 3449 /+ TODO: assert(a.getKeyRef(ch, default_k)[] !is k[]); // on hit doesn't use `default_k` +/ 3450 // assert(a.getKeyRef(ch, default_k)[] == ch); 3451 } 3452 assert(a.length == n); 3453 3454 // remove all 3455 foreach (const i; 0 .. n) { 3456 const char[1] ch = ['a' + i]; 3457 const k = K(ch); // @nogc 3458 assert(a.contains(k)); 3459 assert(a.remove(k)); 3460 assert(!a.contains(k)); 3461 } 3462 assert(a.length == 0); 3463 3464 // insert all again 3465 foreach (const i; 0 .. n) { 3466 const char[1] ch = ['a' + i]; 3467 const k = K(ch); // @nogc 3468 assert(k[] == ch[]); 3469 3470 assert(!a.contains(k)); 3471 assert(!a.contains(ch[])); // @nogc 3472 assert(a.getKeyRef(k, default_k)[] is default_k[]); // on miss use `default_k` 3473 /+ TODO: assert(a.getKeyRef(ch, default_k)[] is default_k[]); // on miss use `default_k` +/ 3474 3475 a[k] = V.init; 3476 } 3477 assert(a.length == n); 3478 3479 X b; 3480 foreach (const i; 0 .. n) { 3481 const char[1] ch = ['a' + (n - 1 - i)]; 3482 const k = K(ch); // @nogc 3483 3484 assert(!b.contains(k)); 3485 3486 b[k] = V.init; 3487 3488 assert(b.contains(k)); 3489 } 3490 3491 assert(a == b); 3492 } 3493 3494 /// 3495 pure nothrow @safe @nogc unittest { 3496 import nxt.address : AlignedAddress; 3497 alias A = AlignedAddress!1; 3498 HybridHashMap!(A, A) m; 3499 static assert(m.sizeof == 3*size_t.sizeof); // assure that hole bitmap is not used 3500 foreach (const address; 1 .. 0x1000) { 3501 const key = address; 3502 const value = 2*address; 3503 assert(A(key) !in m); 3504 m[A(key)] = A(value); 3505 const eq = m[A(key)] == A(value); 3506 assert(eq); 3507 assert(A(key) in m); 3508 } 3509 } 3510 3511 /// 3512 pure nothrow @safe @nogc unittest { 3513 import nxt.sso_string : SSOString; 3514 alias K = SSOString; 3515 alias V = long; 3516 alias X = HybridHashMap!(K, V, hashOf, defaultKeyEqualPredOf!(K), Mallocator, Options(GrowOnlyFlag.no, BorrowCheckFlag.no)); 3517 X x; 3518 } 3519 3520 /// non-nullable key type 3521 version (none) /+ TODO: enable +/ 3522 pure nothrow @safe @nogc unittest { 3523 alias K = long; 3524 alias V = long; 3525 alias X = HybridHashMap!(K, V, hashOf, defaultKeyEqualPredOf!(K), Mallocator, Options(GrowOnlyFlag.no, BorrowCheckFlag.no)); 3526 X x; 3527 } 3528 3529 /** Is `true` iff `T` has a specific value dedicated to representing holes 3530 * (removed/erase) values. 3531 */ 3532 enum isHoleable(T) = (// __traits(hasMember, T, "isHole") && 3533 // __traits(hasMember, T, "holeify") && 3534 __traits(hasMember, T, "holeValue")); 3535 3536 /** Default key equality/equivalence predicate for the type `T`. 3537 */ 3538 template defaultKeyEqualPredOf(T) { 3539 static if (is(T == class)) 3540 // static assert(__traits(hasMember, T, "opEquals"), 3541 // "Type" ~ T.stringof ~ " doesn't have local opEquals() defined"); 3542 // enum defaultKeyEqualPredOf = "a && b && a.opEquals(b)"; 3543 enum defaultKeyEqualPredOf = "a is b"; 3544 // (const T a, const T b) => ((a !is null) && (b !is null) && a.opEquals(b)); 3545 else 3546 enum defaultKeyEqualPredOf = "a == b"; 3547 } 3548 3549 /// 3550 pure nothrow @safe unittest { 3551 class C { 3552 pure nothrow @safe @nogc: 3553 this(int x) { 3554 this.x = x; 3555 } 3556 @property bool opEquals(in typeof(this) rhs) const => x == rhs.x; 3557 @property override bool opEquals(const scope Object rhs) const @trusted { 3558 C rhs_ = cast(C)rhs; 3559 return rhs_ && x == rhs_.x; 3560 } 3561 int x; 3562 } 3563 static assert(defaultKeyEqualPredOf!(C) == "a is b"); 3564 }