1 module nxt.sso_hashmap_or_hashset; 2 3 import nxt.container_traits; 4 5 /** Set insertion status. 6 */ 7 enum SetInsertionStatus 8 { 9 added, // element was added 10 unmodified // element was left unchanged 11 } 12 13 /** Map insertion status. 14 */ 15 enum MapInsertionStatus 16 { 17 added, // element was added 18 modified, // value of element was changed (map only). TODO: only for Map-case 19 unmodified // element was left unchanged 20 } 21 22 /** Hash set (or map) storing (key) elements of type `K` and values of type `V`. 23 * 24 * Uses small-size-optimized (SSO) arrays as bins, having a more stable 25 * worst-case performance than open-addressing. 26 * 27 * Params: 28 * K = key type. 29 * V = value type. 30 * Allocator = memory allocator for bin array and large bins (`LargeBin`) 31 * hasher = hash function or std.digest Hash. 32 * smallBinMinCapacity = minimum capacity of small bin 33 * 34 * See_Also: https://probablydance.com/2017/02/26/i-wrote-the-fastest-hashtable/ 35 * 36 * TODO: support HashSet-in operator: assert(*("a" in s) == "a"); 37 * 38 * TODO: in non-sso optimized store add flag similar to Nullable that reserves a 39 * specific value for key that indicates that slot is unused. Use this when 40 * storing indexes in knet.storage 41 * 42 * TODO: add extractElement that moves it out similar to 43 * http://en.cppreference.com/w/cpp/container/unordered_set/extract 44 * 45 * TODO: add merge or union algorithm here or into container_algorithm.d. See 46 * also: http://en.cppreference.com/w/cpp/container/unordered_set/merge. this 47 * algorithm moves elements from source if they are not already in `this` 48 * 49 * TODO: adjust rehashing to occur when relative number of LargeBuckets is 50 * larger than, say, 1/10. Experiment with different ratios. 51 * 52 * TODO: add template parameter `alias nullKeyValue` that avoids having to store 53 * `bstates` when smallBinCapacity == 1, similar to: 54 * std.typecons.nullable(alias nullValue, T)( T t ) 55 * 56 * TODO: add flag for use of growth factor smaller than powers of two. use prime_modulo.d 57 * 58 * TODO: use core.bitop : bsr, bsl to find first empty element in bin. if as fast 59 * as current find use it to optimize remove() 60 * 61 * TODO: Avoid extra length and capacity in _statuses (length or large) by making 62 * it allocate in sync with bins (using soa.d). 63 * 64 * TODO: also try allocating values in a separate array using soa.d and see if 65 * benchmarks become better 66 * 67 * TODO: growWithExtraCapacity(): if allocator has realloc we can do rehashing in-place similar to 68 * reordering in in-place radix (integer_sorting.d), otherwise rehash into new 69 * copy of bins and free old bins when done. If bin element count is > 70 * 1 this is more complicated since each bin contains a set of elements to 71 * swap out and must be put in a queue. 72 * 73 * TODO: benchmark against https://github.com/greg7mdp/sparsepp 74 */ 75 struct SSOHashMapOrSet(K, V = void, 76 alias Allocator = null, 77 alias hasher = hashOf, 78 uint smallBinMinCapacity = 1, 79 uint capacityScaleNumerator = 2, 80 uint capacityScaleDenominator = 1) 81 if (// !hasAliasing!K && 82 smallBinMinCapacity >= 1) // no use having empty small bins 83 { 84 import core.lifetime : emplace, move, moveEmplace; 85 import core.internal.traits : hasElaborateCopyConstructor, hasElaborateDestructor, Unqual; 86 import nxt.emplace_all : moveEmplaceAllNoReset; 87 import std.traits : isMutable, hasIndirections; 88 import std.algorithm.comparison : max; 89 // TODO: activate and use import nxt.prime_modulo; 90 91 /** In the hash map case, `V` is non-void, and a value is stored alongside 92 * the key of type `K`. 93 */ 94 enum hasValue = !is(V == void); 95 96 alias MutableThis = Unqual!(typeof(this)); 97 alias ConstThis = const(MutableThis); 98 99 pragma(inline): 100 101 /// Element type. 102 static if (hasValue) 103 { 104 alias InsertionStatus = MapInsertionStatus; 105 106 /// Constant element reference with both constant key and value. 107 struct T 108 { 109 K key; 110 V value; 111 } 112 113 /// Mutable element reference with constant key and mutable value. 114 struct CT 115 { 116 const K key; 117 V value; 118 } 119 120 /// Get key part of element. 121 pragma(inline, true) 122 static auto ref inout(K) keyOf()(auto ref return inout(T) element) 123 { 124 return element.key; 125 } 126 127 /// Get reference to key part of `element`. 128 pragma(inline, true) 129 static ref inout(K) keyRefOf()(ref return inout(T) element) // template-lazy 130 { 131 return element.key; 132 } 133 134 /// Get value part of element. 135 pragma(inline, true) 136 static auto ref inout(V) valueOf()(auto ref return inout(T) element) 137 { 138 return element.value; 139 } 140 141 /** Type of key stored. */ 142 alias KeyType = K; 143 144 /** Type of value stored. */ 145 alias ValueType = V; 146 147 enum keyEqualPred = "a.key is b"; 148 } 149 else // HashSet 150 { 151 alias InsertionStatus = SetInsertionStatus; 152 153 alias T = K; 154 155 /// Get key part of element. 156 pragma(inline, true) 157 static auto ref inout(K) keyOf()(auto ref return inout(T) element) 158 { 159 return element; 160 } 161 162 /// Get reference to key part of `element`. 163 pragma(inline, true) 164 static ref inout(K) keyRefOf()(ref return inout(T) element) // template-lazy 165 { 166 return element; 167 } 168 169 enum keyEqualPred = "a is b"; 170 } 171 172 alias ElementType = T; 173 174 /** Make with room for storing at least `capacity` number of elements. 175 */ 176 static typeof(this) withCapacity()(size_t capacity) // template-lazy 177 { 178 version(LDC) pragma(inline, true); 179 return typeof(return)(capacity); 180 } 181 182 import std.traits : isIterable; 183 184 /** Make with `elements`. */ 185 static typeof(this) withElements(R)(R elements) 186 if (isIterable!R) 187 { 188 import std.range.primitives : hasLength; 189 static if (hasLength!R) 190 { 191 typeof(this) that = withCapacity(elements.length); 192 } 193 else 194 { 195 typeof(this) that; // TODO: if `isForwardRange` count elements 196 } 197 foreach (ref element; elements) 198 { 199 that.insert(element); 200 } 201 return that; 202 } 203 204 private static typeof(this) withBinCount()(size_t binCount) // template-lazy 205 { 206 version(LDC) pragma(inline, true); 207 typeof(return) that; // TODO: return direct call to store constructor 208 that._bins = Bins.withLength(binCount); 209 that._bstates = Bstates.withLength(binCount); 210 that._length = 0; 211 return that; 212 } 213 214 /** Construct with room for storing at least `capacity` number of elements. 215 */ 216 private this()(size_t capacity) // template-lazy 217 { 218 immutable binCount = binCountOfCapacity(capacity); 219 _bins = Bins.withLength(binCount); 220 _bstates = Bstates.withLength(binCount); 221 _length = 0; 222 } 223 224 /** Lookup bin count from capacity `capacity`. 225 */ 226 static private size_t binCountOfCapacity()(size_t capacity) // template-lazy 227 { 228 immutable minimumBinCount = ((capacityScaleNumerator * 229 capacity) / 230 (smallBinCapacity * 231 capacityScaleDenominator)); 232 static if (__VERSION__ >= 2098) import std.math.algebraic : nextPow2; else import std.math : nextPow2; 233 return nextPow2(minimumBinCount == 0 ? 234 0 : 235 minimumBinCount - 1); 236 } 237 238 /// Destruct. 239 ~this() @nogc 240 { 241 version(D_Coverage) {} else pragma(inline, true); 242 release(); 243 } 244 245 /// No copying. 246 @disable this(this); 247 248 /// Duplicate. 249 static if (__traits(isCopyable, T)) 250 { 251 typeof(this) dup()() const @trusted // template-lazy 252 { 253 typeof(return) that; 254 255 that._bins.reserve(_bins.length); 256 // TODO: merge these 257 that._bins.length = _bins.length; // TODO: this zero-initializes before initialization below, use unsafe setLengthOnlyUNSAFE 258 259 that._bstates = _bstates.dup; 260 assert(that._bstates[] == _bstates[]); 261 262 that._length = _length; 263 264 foreach (immutable binIx; 0 .. _bins.length) 265 { 266 if (_bstates[binIx].isLarge) 267 { 268 LargeBin.emplaceWithCopiedElements(&that._bins[binIx].large, 269 _bins[binIx].large[]); 270 } 271 else 272 { 273 auto elements = smallBinElementsAt(binIx); 274 /** TODO: functionize to `emplaceAll` in emplace_all.d. See_Also: 275 * http://forum.dlang.org/post/xxigbqqflzwfgycrclyq@forum.dlang.org 276 */ 277 static if (hasElaborateCopyConstructor!T) 278 { 279 foreach (immutable elementIx, immutable ref element; elements) 280 { 281 emplace(&that._bins[binIx].small[elementIx], 282 element); 283 } 284 } 285 else 286 { 287 enum useMemcpy = true; 288 static if (useMemcpy) 289 { 290 // currently faster than slice assignment on else branch 291 import core.stdc.string : memcpy; 292 memcpy(that._bins[binIx].small.ptr, // cannot overlap 293 elements.ptr, 294 elements.length * T.sizeof); 295 } 296 else 297 { 298 that._bins[binIx].small.ptr[0 .. _bstates[binIx].smallCount] = elements; 299 } 300 } 301 } 302 } 303 304 return that; 305 } 306 } 307 308 /// Grow by duplicating number of bins. 309 private void growWithExtraCapacity(size_t extraCapacity) @trusted // not template-lazy 310 { 311 size_t newBinCount = 0; 312 if (extraCapacity == 1) 313 { 314 newBinCount = binCount ? 2 * binCount : 1; // 0 => 1, 1 => 2, 2 => 4, ... 315 } 316 else 317 { 318 newBinCount = binCountOfCapacity(_length + extraCapacity); 319 } 320 auto copy = withBinCount(newBinCount); 321 322 // move elements to copy 323 foreach (immutable binIx; 0 .. _bins.length) 324 { 325 foreach (ref element; binElementsAt(binIx)) 326 { 327 copy.insertMoveWithoutBinCountGrowth(element); 328 } 329 } 330 assert(copy._length == _length); // length shouldn't change 331 332 moveEmplace(copy._bstates, _bstates); // `_bstates` doesn't need destroying 333 move(copy._bins, _bins); 334 335 assert(!_bins.empty); 336 assert(!_bstates.empty); 337 } 338 339 /// Equality. 340 bool opEquals()(in auto ref typeof(this) rhs) const @trusted 341 { 342 if (_length != rhs._length) { return false; } 343 344 foreach (immutable binIx; 0 .. _bins.length) 345 { 346 foreach (const ref element; binElementsAt(binIx)) 347 { 348 static if (hasValue) 349 { 350 auto elementFound = element.key in rhs; 351 if (!elementFound) { return false; } 352 if ((*elementFound) !is element.value) { return false; } 353 } 354 else 355 { 356 if (!rhs.contains(element)) { return false; } 357 } 358 } 359 } 360 361 return true; 362 } 363 364 /// Empty. 365 void clear()() // template-lazy 366 { 367 version(LDC) pragma(inline, true); 368 release(); 369 resetInternalData(); 370 } 371 372 /// Release internal allocations. 373 private void release() @trusted 374 { 375 foreach (immutable binIx; 0 .. _bins.length) 376 { 377 if (_bstates[binIx].isLarge) 378 { 379 static if (hasElaborateDestructor!LargeBin) 380 { 381 .destroy(_bins[binIx].large); 382 } 383 } 384 else 385 { 386 static if (hasElaborateDestructor!T) 387 { 388 // TODO: use emplace_all : destroyAll(smallBinElementsAt(binIx)) 389 foreach (ref element; smallBinElementsAt(binIx)) 390 { 391 .destroy(element); 392 } 393 } 394 } 395 } 396 } 397 398 /// Reset internal data. 399 pragma(inline, true) 400 private void resetInternalData() 401 { 402 _bins.clear(); 403 _bstates.clear(); 404 _length = 0; 405 } 406 407 /** Check if `element` is stored. 408 Returns: `true` if element was already present, `false` otherwise. 409 */ 410 bool contains()(in K key) const // template-lazy. TODO: make `auto ref K` work 411 { 412 version(LDC) pragma(inline, true); 413 if (empty) // TODO: can this check be avoided? 414 { 415 return false; // prevent `RangeError` in `binElementsAt` when empty 416 } 417 immutable binIx = keyToBinIx(key); 418 return hasKey(binElementsAt(binIx), key); 419 } 420 /// ditto 421 bool contains()(in ref K key) const // template-lazy 422 { 423 version(LDC) pragma(inline, true); 424 if (empty) // TODO: can this check be avoided? 425 { 426 return false; // prevent `RangeError` in `binElementsAt` when empty 427 } 428 immutable binIx = keyToBinIx(key); 429 return hasKey(binElementsAt(binIx), key); 430 } 431 432 /** Insert `element`, being either a key-value (map-case) or a just a key (set-case). 433 */ 434 InsertionStatus insert(T element) 435 { 436 version(LDC) pragma(inline, true); 437 reserveExtra(1); 438 return insertMoveWithoutBinCountGrowth(element); 439 } 440 441 /** Insert `elements`, all being either a key-value (map-case) or a just a key (set-case). 442 */ 443 void insertN(R)(R elements) @trusted 444 if (isIterable!R && 445 __traits(isCopyable, T)) 446 { 447 import std.range.primitives : hasLength; 448 static if (hasLength!R) 449 { 450 // reserveExtra(elements.length); // TODO: this fails when starting knet 451 } 452 foreach (element; elements) 453 { 454 // TODO: use `insertMoveWithoutBinCountGrowth` when call to `reserveExtra` works 455 static if (hasIndirections!T) 456 insert(element); 457 else 458 insert(*cast(Unqual!T*)&element); 459 } 460 } 461 462 /** Reserve rom for `extraCapacity` number of extra buckets. */ 463 pragma(inline, true) 464 void reserveExtra()(size_t extraCapacity) 465 { 466 if ((capacityScaleNumerator * 467 (_length + extraCapacity) / 468 capacityScaleDenominator) > 469 _bins.length * smallBinCapacity) 470 { 471 growWithExtraCapacity(extraCapacity); 472 } 473 } 474 475 static if (hasValue) 476 { 477 /** Insert or replace `value` at `key`. */ 478 pragma(inline, true) // LDC must have this 479 InsertionStatus insert(K key, V value) 480 { 481 return insert(T(move(key), 482 move(value))); 483 } 484 } 485 486 static private size_t offsetOfKey(in T[] elements, 487 in ref K key) 488 { 489 version(LDC) pragma(inline, true); 490 size_t elementOffset = 0; 491 foreach (const ref e; elements) 492 { 493 if (keyOf(e) is key) { break; } 494 elementOffset += 1; 495 } 496 return elementOffset; 497 } 498 499 pragma(inline, true) 500 static private bool hasKey(in T[] elements, 501 in ref K key) 502 { 503 return offsetOfKey(elements, key) != elements.length; 504 } 505 506 /** Insert `element` like with `insert()` without automatic growth of number 507 * of bins. 508 */ 509 InsertionStatus insertMoveWithoutBinCountGrowth(ref T element) @trusted // ref simplifies move 510 { 511 immutable binIx = keyToBinIx(keyRefOf(element)); 512 T[] elements = binElementsAt(binIx); 513 immutable elementOffset = offsetOfKey(elements, keyOf(element)); 514 immutable elementFound = elementOffset != elements.length; 515 516 if (elementFound) 517 { 518 static if (hasValue) 519 { 520 /* TODO: Rust does the same in its `insert()` at 521 * https://doc.rust-lang.org/std/collections/struct.HashMap.html 522 */ 523 if (elements[elementOffset].value !is valueOf(element)) // if different value (or identity for classes) 524 { 525 // replace value 526 static if (needsMove!V) 527 { 528 move(valueOf(element), 529 elements[elementOffset].value); 530 } 531 else 532 { 533 elements[elementOffset].value = valueOf(element); 534 } 535 return typeof(return).modified; 536 } 537 } 538 return typeof(return).unmodified; 539 } 540 else // no elementFound 541 { 542 if (_bstates[binIx].isLarge) // stay large 543 { 544 _bins[binIx].large.insertBackMove(element); 545 } 546 else 547 { 548 if (_bstates[binIx].isFullSmall) // expand small to large 549 { 550 static if (needsMove!T) 551 { 552 // TODO: functionize to concatenation:moveConcatenate() 553 T[smallBinCapacity + 1] smallCopy = void; 554 moveEmplaceAllNoReset(_bins[binIx].small[], 555 smallCopy[0 .. smallBinCapacity]); 556 moveEmplace(element, 557 smallCopy[smallBinCapacity]); 558 559 LargeBin.emplaceWithMovedElements(&_bins[binIx].large, 560 smallCopy[]); 561 } 562 else 563 { 564 import nxt.concatenation : concatenate; 565 auto smallCopy = concatenate(_bins[binIx].small, element); 566 emplace!(LargeBin)(&_bins[binIx].large, smallCopy); 567 } 568 _bstates[binIx].makeLarge(); 569 } 570 else // stay small 571 { 572 static if (needsMove!T) 573 { 574 moveEmplace(element, 575 _bins[binIx].small[_bstates[binIx].smallCount]); 576 } 577 else 578 { 579 _bins[binIx].small[_bstates[binIx].smallCount] = element; 580 } 581 _bstates[binIx].incSmallCount(); 582 } 583 } 584 _length += 1; 585 return typeof(return).added; 586 } 587 } 588 589 /** L-value element reference (and in turn range iterator). 590 */ 591 static private struct LvalueElementRef(SomeHashMapOrSet) 592 { 593 SomeHashMapOrSet* table; 594 size_t binIx; // index to bin inside table 595 size_t elementOffset; // offset to element inside bin 596 size_t elementCounter; // counter over number of elements popped 597 598 pragma(inline, true): 599 600 /// Check if empty. 601 @property bool empty() const @safe pure nothrow @nogc 602 { 603 return binIx == table.binCount; 604 } 605 606 /// Get number of element left to pop. 607 @property size_t length() const @safe pure nothrow @nogc 608 { 609 return table.length - elementCounter; 610 } 611 612 void initFirstNonEmptyBin() 613 { 614 while (binIx < table.binCount && 615 table.binElementCountAt(binIx) == 0) 616 { 617 binIx += 1; 618 } 619 } 620 621 pragma(inline) 622 void popFront() 623 { 624 assert(!empty); 625 elementOffset += 1; // next element 626 // if current bin was emptied 627 while (elementOffset >= table.binElementsAt(binIx).length) 628 { 629 // next bin 630 binIx += 1; 631 elementOffset = 0; 632 if (empty) { break; } 633 } 634 elementCounter += 1; 635 } 636 637 @property typeof(this) save() // ForwardRange 638 { 639 return this; 640 } 641 } 642 643 /** R-value element reference (and in turn range iterator). 644 */ 645 static private struct RvalueElementRef(SomeHashMapOrSet) 646 { 647 SomeHashMapOrSet table; // owned 648 size_t binIx; // index to bin inside table 649 size_t elementOffset; // offset to element inside bin 650 size_t elementCounter; // counter over number of elements popped 651 652 pragma(inline, true): 653 654 /// Check if empty. 655 @property bool empty() const @safe pure nothrow @nogc 656 { 657 return binIx == table.binCount; 658 } 659 660 /// Get number of element left to pop. 661 @property size_t length() const @safe pure nothrow @nogc 662 { 663 return table.length - elementCounter; 664 } 665 666 void initFirstNonEmptyBin() 667 { 668 while (binIx < table.binCount && 669 table.binElementCountAt(binIx) == 0) 670 { 671 binIx += 1; 672 } 673 } 674 675 pragma(inline) 676 void popFront() 677 { 678 assert(!empty); 679 elementOffset += 1; // next element 680 // if current bin was emptied 681 while (elementOffset >= table.binElementsAt(binIx).length) 682 { 683 // next bin 684 binIx += 1; 685 elementOffset = 0; 686 if (empty) { break; } 687 } 688 elementCounter += 1; 689 } 690 } 691 692 static if (!hasValue) // HashSet 693 { 694 pragma(inline, true) 695 bool opBinaryRight(string op)(in K key) inout @trusted 696 if (op == "in") 697 { 698 return contains(key); 699 } 700 701 /// Range over elements of l-value instance of this. 702 static private struct ByLvalueElement(SomeHashMapOrSet) 703 { 704 pragma(inline, true): 705 static if (is(ElementType == class)) 706 { 707 /// Get reference to front element (key and value). 708 @property scope auto front()() return @trusted 709 { 710 /* cast away const from `SomeHashMapOrSet` for classes 711 * because class elements are currently hashed and compared 712 * compared using their identity (pointer value) `is` 713 */ 714 return cast(ElementType)table.binElementsAt(binIx)[elementOffset]; 715 } 716 } 717 else 718 { 719 /// Get reference to front element (key and value). 720 @property scope auto front()() 721 { 722 return table.binElementsAt(binIx)[elementOffset]; 723 } 724 } 725 public LvalueElementRef!SomeHashMapOrSet _elementRef; 726 alias _elementRef this; 727 } 728 729 /// Range over elements of r-value instance of this. 730 static private struct ByRvalueElement(SomeHashMapOrSet) 731 { 732 pragma(inline, true): 733 static if (is(ElementType == class)) 734 { 735 /// Get reference to front element (key and value). 736 @property scope auto front()() return @trusted 737 { 738 /* cast away const from `SomeHashMapOrSet` for classes 739 * because class elements are currently hashed and compared 740 * compared using their identity (pointer value) `is` 741 */ 742 return cast(ElementType)table.binElementsAt(binIx)[elementOffset]; 743 } 744 } 745 else 746 { 747 /// Get reference to front element (key and value). 748 @property scope auto front()() 749 { 750 return table.binElementsAt(binIx)[elementOffset]; 751 } 752 } 753 public RvalueElementRef!SomeHashMapOrSet _elementRef; 754 alias _elementRef this; 755 } 756 757 /// ditto 758 version(none) // cannot be combined 759 { 760 pragma(inline, true) 761 scope auto opSlice()() inout return // template-lazy 762 { 763 return byElement(); 764 } 765 } 766 } 767 768 static if (hasValue) // HashMap 769 { 770 scope inout(V)* opBinaryRight(string op)(in K key) inout @trusted return 771 if (op == "in") 772 { 773 if (empty) 774 { 775 // prevent range error in `binElementsAt` when `this` is empty 776 return typeof(return).init; 777 } 778 immutable binIx = keyToBinIx(key); 779 const elements = binElementsAt(binIx); 780 immutable elementOffset = offsetOfKey(elements, key); 781 immutable elementFound = elementOffset != elements.length; 782 if (elementFound) 783 { 784 return cast(typeof(return))&elements[elementOffset].value; 785 // return typeof(return)(&this, binIx, elementOffset); 786 } 787 else // miss 788 { 789 return typeof(return).init; 790 } 791 } 792 793 static private struct ByKey(SomeHashMapOrSet) 794 { 795 pragma(inline, true): 796 /// Get reference to key of front element. 797 @property const scope auto ref front()() return // key access must be const 798 { 799 return table.binElementsAt(binIx)[elementOffset].key; 800 } 801 public LvalueElementRef!SomeHashMapOrSet _elementRef; 802 alias _elementRef this; 803 } 804 805 /// Returns forward range that iterates through the keys of `this`. 806 @property scope auto byKey()() inout return @trusted // template-lazy property 807 { 808 alias This = ConstThis; 809 auto result = ByKey!This((LvalueElementRef!This(cast(This*)&this))); 810 result.initFirstNonEmptyBin(); 811 return result; 812 } 813 814 static private struct ByValue(SomeHashMapOrSet) 815 { 816 pragma(inline, true): 817 /// Get reference to value of front element. 818 @property scope auto ref front()() return @trusted // template-lazy property. TODO: remove @trusted 819 { 820 return *(cast(ValueType*)(&table.binElementsAt(binIx)[elementOffset].value)); // TODO: remove reinterpret cast 821 } 822 public LvalueElementRef!SomeHashMapOrSet _elementRef; 823 alias _elementRef this; 824 } 825 826 /// Returns forward range that iterates through the values of `this`. 827 @property scope auto byValue()() inout return @trusted // template-lazy property 828 { 829 alias This = ConstThis; 830 auto result = ByValue!This((LvalueElementRef!This(cast(This*)&this))); 831 result.initFirstNonEmptyBin(); 832 return result; 833 } 834 835 static private struct ByKeyValue(SomeHashMapOrSet) 836 { 837 pragma(inline, true): 838 /// Get reference to front element (key and value). 839 @property scope auto ref front()() return @trusted 840 { 841 static if (isMutable!(SomeHashMapOrSet)) 842 { 843 alias E = CT; 844 } 845 else 846 { 847 alias E = const(T); 848 } 849 return *(cast(E*)&table.binElementsAt(binIx)[elementOffset]); // TODO: remove cast 850 } 851 public LvalueElementRef!SomeHashMapOrSet _elementRef; 852 alias _elementRef this; 853 } 854 855 /// Returns forward range that iterates through the keys and values of `this`. 856 @property scope auto byKeyValue()() return @trusted // template-lazy property 857 { 858 alias This = MutableThis; 859 auto result = ByKeyValue!This((LvalueElementRef!This(cast(This*)&this))); 860 result.initFirstNonEmptyBin(); 861 return result; 862 } 863 /// ditto 864 @property scope auto byKeyValue()() const return @trusted // template-lazy property 865 { 866 alias This = ConstThis; 867 auto result = ByKeyValue!This((LvalueElementRef!This(cast(This*)&this))); 868 result.initFirstNonEmptyBin(); 869 return result; 870 } 871 872 /// ditto 873 pragma(inline, true) 874 scope auto opSlice()() return // template-lazy 875 { 876 return byKeyValue(); 877 } 878 879 /// Indexing. 880 scope ref inout(V) opIndex()(in K key) inout return // auto ref here makes things slow 881 { 882 version(LDC) pragma(inline, true); 883 immutable binIx = keyToBinIx(key); 884 auto elements = binElementsAt(binIx); 885 886 immutable elementOffset = offsetOfKey(elements, key); 887 immutable elementFound = elementOffset != elements.length; 888 if (elementFound) 889 { 890 return binElementsAt(binIx)[elementOffset].value; 891 } 892 version(assert) 893 { 894 import core.exception : RangeError; 895 throw new RangeError("Key not found"); 896 } 897 } 898 899 /** Get value of `key` or `defaultValue` if `key` not present (and 900 * therefore `nothrow`). 901 * 902 * Returns: value reference iff `defaultValue` is an l-value. 903 * 904 * TODO: make `defaultValue` `lazy` when that can be `nothrow` 905 */ 906 auto ref V get()(in K key, in V defaultValue) @trusted // auto ref here makes things slow 907 { 908 import std.algorithm.searching : countUntil; 909 immutable binIx = keyToBinIx(key); 910 immutable ptrdiff_t elementOffset = binElementsAt(binIx).countUntil!(_ => _.key is key); // TODO: functionize 911 if (elementOffset != -1) // elementFound 912 { 913 return binElementsAt(binIx)[elementOffset].value; 914 } 915 else // miss 916 { 917 return defaultValue; 918 } 919 } 920 921 /** Supports $(B aa[key] = value;) syntax. 922 */ 923 void opIndexAssign()(V value, K key) // template-lazy 924 { 925 version(LDC) pragma(inline, true); 926 insert(T(move(key), 927 move(value))); 928 // TODO: return reference to value 929 } 930 931 static if (__traits(compiles, { V _; _ += 1; })) // if we can increase the key 932 { 933 /** Increase value at `key`, or set value to 1 if `key` hasn't yet 934 * been added. 935 */ 936 pragma(inline, true) 937 void autoinitIncAt()(in K key) // template-lazy 938 { 939 auto elementFound = key in this; 940 if (elementFound) 941 { 942 (*elementFound) += 1; 943 } 944 else 945 { 946 insert(key, V.init + 1); 947 } 948 } 949 } 950 951 } 952 953 /** Remove `element` and, when possible, shrink its large bin to small. 954 955 Returns: `true` if element was removed, `false` otherwise. 956 */ 957 bool remove()(in K key) // template-lazy 958 @trusted 959 { 960 immutable binIx = keyToBinIx(key); 961 import nxt.container_algorithm : popFirstMaybe; 962 if (_bstates[binIx].isLarge) 963 { 964 immutable elementFound = _bins[binIx].large.popFirstMaybe!keyEqualPred(key); 965 _length -= elementFound ? 1 : 0; 966 if (elementFound) 967 { 968 tryShrinkLargeBinAt(binIx); 969 } 970 return elementFound; 971 } 972 else 973 { 974 const elements = smallBinElementsAt(binIx); 975 immutable elementIx = offsetOfKey(elements, key); 976 immutable elementFound = elementIx != elements.length; 977 if (elementFound) 978 { 979 removeSmallElementAt(binIx, elementIx); 980 } 981 _length -= elementFound ? 1 : 0; 982 return elementFound; 983 } 984 } 985 986 /** Remove small element at `elementIx` in bin `binIx`. */ 987 private void removeSmallElementAt()(size_t binIx, // template-lazy 988 size_t elementIx) 989 { 990 assert(!_bstates[binIx].isLarge); 991 import nxt.container_algorithm : shiftToFrontAt; 992 smallBinElementsAt(binIx).shiftToFrontAt(elementIx); 993 _bstates[binIx].decSmallCount(); 994 static if (hasElaborateDestructor!T) 995 { 996 .destroy(_bins[binIx].small[_bstates[binIx].smallCount]); // TODO: this is incorrect 997 } 998 } 999 1000 /** Shrink large bin at `binIx` possible posbiel. */ 1001 private void tryShrinkLargeBinAt()(size_t binIx) // template-lazy 1002 { 1003 assert(_bstates[binIx].isLarge); 1004 immutable count = _bins[binIx].large.length; 1005 if (count <= smallBinCapacity) // large fits in small 1006 { 1007 SmallBin smallCopy = void; 1008 moveEmplaceAllNoReset(_bins[binIx].large[0 .. count], 1009 smallCopy[0 .. count]); 1010 static if (hasElaborateDestructor!LargeBin) 1011 { 1012 .destroy(_bins[binIx].large); 1013 } 1014 moveEmplaceAllNoReset(smallCopy[0 .. count], 1015 _bins[binIx].small[0 .. count]); 1016 _bstates[binIx].smallCount = count; 1017 } 1018 } 1019 1020 /** Rehash. 1021 * 1022 * Reorganize `this` in place so that lookups are more efficient. 1023 */ 1024 ref typeof(this) rehash()() @trusted // template-lazy 1025 { 1026 static assert(0, "TODO: remove template parens of this functions and implement"); 1027 // return this; 1028 } 1029 1030 /// Check if empty. 1031 pragma(inline, true) 1032 @property bool empty() const { return _length == 0; } 1033 1034 /// Get length (read-only). 1035 pragma(inline, true) 1036 @property size_t length() const { return _length; } 1037 1038 /// Get bin count. 1039 pragma(inline, true) 1040 @property size_t binCount() const { return _bins.length; } 1041 1042 /// Bin count statistics. 1043 struct BinCounts 1044 { 1045 size_t smallCount; // number of hybrid bins being small 1046 size_t largeCount; // number of hybrid bins being large 1047 } 1048 1049 /// Returns: bin count statistics for small and large bins. 1050 pragma(inline, false) 1051 BinCounts binCounts()() const // template-lazy 1052 { 1053 import std.algorithm : count; 1054 immutable largeCount = _bstates[].count!(_ => _.isLarge); 1055 immutable smallCount = _bstates.length - largeCount; 1056 auto result = typeof(return)(smallCount, 1057 largeCount); 1058 assert(result.largeCount + result.smallCount == _bstates.length); 1059 return result; 1060 } 1061 1062 /** Returns: elements in bin at `binIx`. */ 1063 pragma(inline, true) 1064 private scope inout(T)[] binElementsAt(size_t binIx) inout return @trusted 1065 { 1066 if (_bstates[binIx].isLarge) 1067 { 1068 return _bins[binIx].large[]; 1069 } 1070 else 1071 { 1072 return smallBinElementsAt(binIx); 1073 } 1074 } 1075 1076 pragma(inline, true) 1077 private scope inout(T)[] smallBinElementsAt(size_t binIx) inout return 1078 { 1079 return _bins[binIx].small[0 .. _bstates[binIx].smallCount]; 1080 } 1081 1082 /** Returns: number of elements in bin at `binIx`. */ 1083 pragma(inline, true) 1084 private size_t binElementCountAt(size_t binIx) const @trusted 1085 { 1086 if (_bstates[binIx].isLarge) 1087 { 1088 return _bins[binIx].large.length; 1089 } 1090 else 1091 { 1092 return _bstates[binIx].smallCount; 1093 } 1094 } 1095 1096 /** Maximum number of elements that fits in a small bin 1097 * (`SmallBin`). 1098 */ 1099 enum smallBinCapacity = max(smallBinMinCapacity, 1100 LargeBin.sizeof / T.sizeof); 1101 1102 private: 1103 import nxt.dynamic_array : Array = DynamicArray; 1104 1105 /** 32-bit capacity and length for LargeBinLnegth on 64-bit platforms 1106 * saves one word and makes insert() and contains() significantly faster */ 1107 alias LargeBinCapacity = uint; 1108 alias LargeBin = Array!(T, Allocator, LargeBinCapacity); 1109 1110 alias SmallBin = T[smallBinCapacity]; 1111 1112 /// no space for `SmallBin`s should be wasted 1113 static assert(SmallBin.sizeof >= LargeBin.sizeof); 1114 1115 /** Small-size-optimized bin. 1116 */ 1117 union HybridBin 1118 { 1119 SmallBin small; 1120 LargeBin large; 1121 } 1122 1123 static if (mustAddGCRange!LargeBin) 1124 { 1125 static assert(mustAddGCRange!HybridBin, "HybridBin mustAddGCRange when LargeBin is " ~ LargeBin.stringof); 1126 } 1127 static if (mustAddGCRange!SmallBin) 1128 { 1129 static assert(mustAddGCRange!HybridBin, "HybridBin mustAddGCRange when SmallBin is " ~ SmallBin.stringof); 1130 } 1131 1132 /** Count and large status of bin. */ 1133 struct Bstate 1134 { 1135 alias Count = ubyte; 1136 1137 pragma(inline, true): 1138 1139 @property Count smallCount() const 1140 { 1141 assert(_count <= smallBinCapacity); 1142 return _count; 1143 } 1144 1145 @property void smallCount(size_t count) 1146 { 1147 assert(count <= smallBinCapacity); 1148 _count = cast(Count)count; 1149 } 1150 1151 void decSmallCount() 1152 { 1153 assert(_count >= 1); 1154 _count -= 1; 1155 } 1156 1157 void incSmallCount() 1158 { 1159 assert(_count + 1 <= smallBinCapacity); 1160 _count += 1; 1161 } 1162 1163 /** Returns: `true` iff `this` is a large bin. */ 1164 @property bool isLarge() const 1165 { 1166 return _count == Count.max; 1167 } 1168 1169 void makeLarge() 1170 { 1171 _count = Count.max; 1172 } 1173 1174 /** Returns: `true` iff `this` is a full small bin. */ 1175 @property bool isFullSmall() const 1176 { 1177 return _count == smallBinCapacity; 1178 } 1179 1180 Count _count; 1181 } 1182 1183 /** Small-size-optimized bin array. 1184 Size-state (including small or large) for each element is determined by 1185 corresponding element in `Bstates`. 1186 */ 1187 alias Bins = Array!(HybridBin, Allocator); 1188 1189 /// Bin states. 1190 alias Bstates = Array!(Bstate, Allocator); 1191 1192 // TODO: merge these into an instance of soa.d and remove invariant 1193 Bins _bins; // bin elements 1194 Bstates _bstates; // bin states 1195 invariant 1196 { 1197 assert(_bins.length == 1198 _bstates.length); 1199 } 1200 1201 size_t _length; // total number of elements stored 1202 1203 /** Returns: bin index of `hash`. */ 1204 pragma(inline, true) 1205 size_t hashToIndex(hash_t hash) const 1206 { 1207 return hash & powerOf2Mask; 1208 } 1209 1210 /** Returns: bin index of `key`. */ 1211 size_t keyToBinIx()(in auto ref K key) const 1212 { 1213 version(LDC) pragma(inline, true); 1214 import nxt.digestion : hashOf2; 1215 return hashToIndex(hashOf2!(hasher)(key)); 1216 } 1217 1218 /** Returns: current index mask from bin count. */ 1219 pragma(inline, true) 1220 private size_t powerOf2Mask() const 1221 { 1222 immutable typeof(return) mask = _bins.length - 1; 1223 assert((~mask ^ mask) == size_t.max); // isPowerOf2(_bins.length) 1224 return mask; 1225 } 1226 } 1227 1228 /** Hash map storing keys of type `K` and values of type `V`. 1229 */ 1230 alias SSOHashMap(K, V, 1231 alias Allocator = null, 1232 alias hasher = hashOf, 1233 uint smallBinMinCapacity = 1) = SSOHashMapOrSet!(K, V, 1234 Allocator, 1235 hasher, 1236 smallBinMinCapacity); 1237 1238 /** Hash map storing keys of type `K`. 1239 */ 1240 alias SSOHashSet(K, 1241 alias Allocator = null, 1242 alias hasher = hashOf, 1243 uint smallBinMinCapacity = 1) = SSOHashMapOrSet!(K, void, 1244 Allocator, 1245 hasher, 1246 smallBinMinCapacity); 1247 1248 import std.traits : isInstanceOf; 1249 import std.functional : unaryFun; 1250 1251 /** Remove all elements in `x` matching `predicate`. 1252 TODO: move to container_algorithm.d. 1253 */ 1254 void removeAllMatching(alias predicate, SomeHashMapOrSet)(auto ref SomeHashMapOrSet x) 1255 @trusted 1256 if (isInstanceOf!(SSOHashMapOrSet, 1257 SomeHashMapOrSet)) 1258 { 1259 import core.lifetime : moveEmplace; 1260 foreach (immutable binIx; 0 .. x._bins.length) 1261 { 1262 if (x._bstates[binIx].isLarge) 1263 { 1264 import nxt.dynamic_array : remove; 1265 immutable removeCount = x._bins[binIx].large.remove!predicate(); 1266 x._length -= removeCount; 1267 x.tryShrinkLargeBinAt(binIx); 1268 } 1269 else 1270 { 1271 SomeHashMapOrSet.SmallBin tmpSmall; 1272 SomeHashMapOrSet.Bstate tmpBstate; 1273 foreach (ref element; x.smallBinElementsAt(binIx)) 1274 { 1275 if (unaryFun!predicate(element)) 1276 { 1277 import core.internal.traits : hasElaborateDestructor; 1278 static if (hasElaborateDestructor!(SomeHashMapOrSet.T)) 1279 { 1280 destroy(element); 1281 } 1282 x._length -= 1; 1283 } 1284 else 1285 { 1286 moveEmplace(element, tmpSmall[tmpBstate.smallCount()]); 1287 tmpBstate.incSmallCount(); 1288 } 1289 } 1290 assert(!tmpBstate.isLarge); // should stay small 1291 moveEmplace(tmpSmall, x._bins[binIx].small); 1292 moveEmplace(tmpBstate, x._bstates[binIx]); 1293 } 1294 } 1295 } 1296 1297 /** Returns: `x` eagerly filtered on `predicate`. 1298 TODO: move to container_algorithm.d. 1299 */ 1300 SomeHashMapOrSet filtered(alias predicate, SomeHashMapOrSet)(SomeHashMapOrSet x) 1301 @trusted 1302 if (isInstanceOf!(SSOHashMapOrSet, 1303 SomeHashMapOrSet)) 1304 { 1305 import std.functional : not; 1306 removeAllMatching!(not!predicate)(x); 1307 import std.algorithm.mutation : move; 1308 return move(x); 1309 } 1310 1311 /** Returns: `x` eagerly intersected with `y`. 1312 TODO: move to container_algorithm.d. 1313 */ 1314 auto intersectedWith(C1, C2)(C1 x, auto ref C2 y) 1315 if (isInstanceOf!(SSOHashMapOrSet, C1) && 1316 isInstanceOf!(SSOHashMapOrSet, C2)) 1317 { 1318 import std.algorithm.mutation : move; 1319 static if (__traits(isRef, y)) // y is l-value 1320 { 1321 // @("complexity", "O(x.length)") 1322 return move(x).filtered!(_ => y.contains(_)); // only x can be reused 1323 } 1324 else 1325 { 1326 /* both are r-values so reuse the shortest */ 1327 // @("complexity", "O(min(x.length), min(y.length))") 1328 if (x.length < 1329 y.length) 1330 { 1331 return move(x).filtered!(_ => y.contains(_)); 1332 } 1333 else 1334 { 1335 return move(y).filtered!(_ => x.contains(_)); 1336 } 1337 } 1338 } 1339 1340 /// r-value and l-value intersection 1341 @safe pure nothrow @nogc unittest 1342 { 1343 alias K = uint; 1344 alias X = SSOHashMapOrSet!(K, void, null, FNV!(64, true)); 1345 1346 auto x = X.withElements([12, 13].s); 1347 auto y = X.withElements([10, 12, 13, 15].s).intersectedWith(x); 1348 assert(y.length == 2); 1349 assert(y.contains(12)); 1350 assert(y.contains(13)); 1351 } 1352 1353 /// r-value and r-value intersection 1354 @safe pure nothrow @nogc unittest 1355 { 1356 alias K = uint; 1357 alias X = SSOHashMapOrSet!(K, void, null, FNV!(64, true)); 1358 1359 auto y = X.withElements([10, 12, 13, 15].s).intersectedWith(X.withElements([12, 13].s)); 1360 assert(y.length == 2); 1361 assert(y.contains(12)); 1362 assert(y.contains(13)); 1363 } 1364 1365 /** Returns: `x` eagerly intersected with `y`. 1366 TODO: move to container_algorithm.d. 1367 */ 1368 auto intersectWith(C1, C2)(ref C1 x, 1369 auto ref const(C2) y) 1370 if (isInstanceOf!(SSOHashMapOrSet, C1) && 1371 isInstanceOf!(SSOHashMapOrSet, C2)) 1372 { 1373 return x.removeAllMatching!(_ => !y.contains(_)); 1374 } 1375 1376 /// r-value and l-value intersection 1377 @safe pure nothrow @nogc unittest 1378 { 1379 alias K = uint; 1380 alias X = SSOHashMapOrSet!(K, void, null, FNV!(64, true)); 1381 1382 auto x = X.withElements([12, 13].s); 1383 auto y = X.withElements([10, 12, 13, 15].s); 1384 y.intersectWith(x); 1385 assert(y.length == 2); 1386 assert(y.contains(12)); 1387 assert(y.contains(13)); 1388 } 1389 1390 /// Returns forward range that iterates through the elements of `c`. 1391 auto byElement(SomeHashMapOrSet)(auto ref inout(SomeHashMapOrSet) c) 1392 @trusted 1393 if (isInstanceOf!(SSOHashMapOrSet, 1394 SomeHashMapOrSet)) 1395 { 1396 alias C = const(SomeHashMapOrSet); 1397 static if (__traits(isRef, c)) 1398 { 1399 auto result = C.ByLvalueElement!C((C.LvalueElementRef!C(cast(C*)&c))); 1400 result.initFirstNonEmptyBin(); 1401 return result; 1402 } 1403 else 1404 { 1405 import std.algorithm.mutation : move; 1406 auto result = C.ByRvalueElement!C((C.RvalueElementRef!C(move(*(cast(SomeHashMapOrSet*)&c))))); // reinterpret 1407 result.initFirstNonEmptyBin(); 1408 return move(result); 1409 } 1410 } 1411 alias range = byElement; // EMSI-container naming 1412 1413 @safe: 1414 1415 /// make range from l-value and r-value. element access is always const 1416 pure nothrow @nogc unittest 1417 { 1418 alias K = uint; 1419 alias X = SSOHashMapOrSet!(K, void, null, FNV!(64, true)); 1420 1421 immutable a = [11, 22].s; 1422 1423 // mutable 1424 auto x = X.withElements(a); 1425 foreach (e; x.byElement) // from l-value 1426 { 1427 static assert(is(typeof(e) == const(K))); // always const access 1428 } 1429 1430 // const 1431 const y = X.withElements(a); 1432 foreach (e; y.byElement) // from l-value 1433 { 1434 static assert(is(typeof(e) == const(K))); 1435 } 1436 1437 foreach (e; X.withElements([11].s).byElement) // from r-value 1438 { 1439 static assert(is(typeof(e) == const(K))); // always const access 1440 } 1441 } 1442 1443 /// test various things 1444 pure nothrow @nogc unittest 1445 { 1446 immutable n = 600; 1447 1448 alias K = uint; 1449 1450 import std.meta : AliasSeq; 1451 foreach (V; AliasSeq!(void, string)) 1452 { 1453 alias X = SSOHashMapOrSet!(K, V, null, FNV!(64, true)); 1454 1455 static if (!X.hasValue) 1456 { 1457 auto x = X.withElements([11, 12, 13].s); 1458 1459 import std.algorithm : count; 1460 auto xr = x.byElement; 1461 1462 alias R = typeof(xr); 1463 import std.range.primitives : isInputRange; 1464 import std.traits : ReturnType; 1465 static assert(is(typeof(R.init) == R)); 1466 static assert(is(ReturnType!((R xr) => xr.empty) == bool)); 1467 auto f = xr.front; 1468 static assert(is(typeof((R xr) => xr.front))); 1469 static assert(!is(ReturnType!((R xr) => xr.front) == void)); 1470 static assert(is(typeof((R xr) => xr.popFront))); 1471 1472 static assert(isInputRange!(typeof(xr))); 1473 1474 assert(x.byElement.count == 3); 1475 1476 X y; 1477 foreach (const ref e; x.byElement) 1478 { 1479 y.insert(e); 1480 } 1481 1482 assert(y.byElement.count == 3); 1483 assert(x == y); 1484 1485 const z = X(); 1486 assert(z.byElement.count == 0); 1487 1488 immutable w = X(); 1489 assert(w.byElement.count == 0); 1490 1491 { 1492 auto xc = X.withElements([11, 12, 13].s); 1493 assert(xc.length == 3); 1494 assert(xc.contains(11)); 1495 1496 // TODO: http://forum.dlang.org/post/kvwrktmameivubnaifdx@forum.dlang.org 1497 xc.removeAllMatching!(_ => _ == 11); 1498 1499 assert(xc.length == 2); 1500 assert(!xc.contains(11)); 1501 1502 xc.removeAllMatching!(_ => _ == 12); 1503 assert(!xc.contains(12)); 1504 assert(xc.length == 1); 1505 1506 xc.removeAllMatching!(_ => _ == 13); 1507 assert(!xc.contains(13)); 1508 assert(xc.length == 0); 1509 1510 // this is ok 1511 foreach (e; xc.byElement) {} 1512 1513 } 1514 1515 { 1516 auto k = X.withElements([11, 12].s).filtered!(_ => _ != 11).byElement; 1517 static assert(isInputRange!(typeof(k))); 1518 assert(k.front == 12); 1519 k.popFront(); 1520 assert(k.empty); 1521 } 1522 1523 { 1524 X q; 1525 auto qv = [11U, 12U, 13U, 14U].s; 1526 q.insertN(qv); 1527 foreach (e; qv[]) 1528 { 1529 assert(q.contains(e)); 1530 } 1531 q.clear(); 1532 assert(q.empty); 1533 } 1534 } 1535 1536 import nxt.container_traits : mustAddGCRange; 1537 static if (X.hasValue && 1538 is(V == string)) 1539 { 1540 static assert(mustAddGCRange!V); 1541 static assert(mustAddGCRange!(V[1])); 1542 static assert(mustAddGCRange!(X.T)); 1543 static assert(mustAddGCRange!(X.SmallBin)); 1544 static assert(!mustAddGCRange!(X.LargeBin)); 1545 } 1546 else 1547 { 1548 static assert(!mustAddGCRange!(X.T)); 1549 static assert(!mustAddGCRange!(X.SmallBin), "Fails for X being " ~ X.SmallBin.stringof); 1550 } 1551 1552 auto x1 = X(); // start empty 1553 1554 // all bins start small 1555 assert(x1.binCounts.largeCount == 0); 1556 1557 // fill x1 1558 1559 foreach (immutable key; 0 .. n) 1560 { 1561 static if (X.hasValue) 1562 { 1563 const value = V.init; 1564 const element = X.ElementType(key, value); 1565 } 1566 else 1567 { 1568 const element = key; 1569 } 1570 1571 assert(key !in x1); 1572 1573 assert(x1.length == key); 1574 assert(x1.insert(element) == X.InsertionStatus.added); 1575 1576 static if (X.hasValue) 1577 { 1578 const e2 = X.ElementType(key, "a"); 1579 assert(x1.insert(e2) == X.InsertionStatus.modified); 1580 assert(x1.contains(key)); 1581 assert(x1.get(key, null) == "a"); 1582 x1.remove(key); 1583 x1[key] = value; 1584 } 1585 1586 assert(x1.length == key + 1); 1587 1588 assert(key in x1); 1589 static if (X.hasValue) 1590 { 1591 auto elementFound = key in x1; 1592 assert(elementFound); 1593 assert(*elementFound != "_"); 1594 } 1595 1596 assert(x1.insert(element) == X.InsertionStatus.unmodified); 1597 static if (X.hasValue) 1598 { 1599 assert(x1.insert(key, value) == X.InsertionStatus.unmodified); 1600 } 1601 assert(x1.length == key + 1); 1602 1603 assert(key in x1); 1604 } 1605 1606 static if (X.hasValue) 1607 { 1608 import nxt.dynamic_array : Array = DynamicArray; 1609 Array!(X.ElementType) a1; 1610 1611 foreach (const ref key; x1.byKey) 1612 { 1613 auto keyPtr = key in x1; 1614 assert(keyPtr); 1615 a1 ~= X.ElementType(key, (*keyPtr)); 1616 } 1617 1618 assert(x1.length == a1.length); 1619 1620 foreach (aElement; a1[]) 1621 { 1622 auto keyPtr = aElement.key in x1; 1623 assert(keyPtr); 1624 assert((*keyPtr) is aElement.value); 1625 } 1626 } 1627 1628 assert(x1.length == n); 1629 1630 // duplicate x1 1631 1632 auto x2 = x1.dup; 1633 1634 // non-symmetric algorithm so both are needed 1635 assert(x2 == x1); 1636 assert(x1 == x2); 1637 1638 static if (X.hasValue) 1639 { 1640 assert(equal(x1.byKey, x2.byKey)); 1641 assert(equal(x1.byValue, x2.byValue)); 1642 assert(equal(x1.byKeyValue, x2.byKeyValue)); 1643 assert(equal(x1[], x2[])); 1644 } 1645 1646 assert(x1.binCounts.largeCount == 1647 x2.binCounts.largeCount); 1648 1649 static assert(!__traits(compiles, { const _ = x1 < x2; })); // no ordering 1650 1651 assert(x2.length == n); 1652 1653 // empty x1 1654 1655 foreach (immutable key; 0 .. n) 1656 { 1657 static if (X.hasValue) 1658 { 1659 const element = X.ElementType(key, V.init); 1660 } 1661 else 1662 { 1663 const element = key; 1664 } 1665 1666 assert(x1.length == n - key); 1667 1668 auto elementFound = key in x1; 1669 assert(elementFound); 1670 static if (X.hasValue) 1671 { 1672 assert(*elementFound is element.value); 1673 } 1674 1675 assert(x1.remove(key)); 1676 assert(x1.length == n - key - 1); 1677 1678 static if (!X.hasValue) 1679 { 1680 assert(!x1.contains(key)); 1681 } 1682 assert(key !in x1); 1683 assert(!x1.remove(key)); 1684 assert(x1.length == n - key - 1); 1685 } 1686 1687 assert(x1.binCounts.largeCount == 0); 1688 1689 assert(x1.length == 0); 1690 1691 x1.clear(); 1692 assert(x1.length == 0); 1693 1694 // empty x2 1695 1696 assert(x2.length == n); // should be not affected by emptying of x1 1697 1698 foreach (immutable key; 0 .. n) 1699 { 1700 static if (X.hasValue) 1701 { 1702 const element = X.ElementType(key, V.init); 1703 } 1704 else 1705 { 1706 const element = key; 1707 } 1708 1709 assert(x2.length == n - key); 1710 1711 assert(key in x2); 1712 1713 assert(x2.remove(key)); 1714 assert(x2.length == n - key - 1); 1715 1716 assert(key !in x2); 1717 assert(!x2.remove(key)); 1718 assert(x2.length == n - key - 1); 1719 } 1720 1721 assert(x2.binCounts.largeCount == 0); 1722 1723 assert(x2.length == 0); 1724 1725 x2.clear(); 1726 assert(x2.length == 0); 1727 } 1728 } 1729 1730 /// range checking 1731 @trusted pure unittest 1732 { 1733 import std.exception : assertThrown, assertNotThrown; 1734 import core.exception : RangeError; 1735 import nxt.uncopyable_sample : V = SomeUncopyable; 1736 1737 immutable n = 11; 1738 1739 alias K = uint; 1740 1741 alias X = SSOHashMapOrSet!(K, V, null, FNV!(64, true)); 1742 1743 auto s = X.withCapacity(n); 1744 1745 void dummy(ref V value) {} 1746 1747 assertThrown!RangeError(dummy(s[K.init])); 1748 1749 foreach (immutable uint i; 0 .. n) 1750 { 1751 s[i] = V(i); 1752 assertNotThrown!RangeError(dummy(s[i])); 1753 } 1754 1755 foreach (immutable uint i; 0 .. n) 1756 { 1757 s.remove(i); 1758 assertThrown!RangeError(dummy(s[i])); 1759 } 1760 1761 s[K.init] = V.init; 1762 auto vp = K.init in s; 1763 static assert(is(typeof(vp) == V*)); 1764 assert((*vp) == V.init); 1765 1766 s.remove(K.init); 1767 assert(K.init !in s); 1768 1769 X t; 1770 t.reserveExtra(4096); 1771 assert(t.binCount == 8192); 1772 1773 t.clear(); 1774 } 1775 1776 /// class as value 1777 @trusted pure unittest 1778 { 1779 import std.exception : assertThrown, assertNotThrown; 1780 import core.exception : RangeError; 1781 1782 immutable n = 11; 1783 1784 alias K = uint; 1785 class V 1786 { 1787 this(uint data) { this.data = data; } 1788 uint data; 1789 } 1790 1791 alias X = SSOHashMapOrSet!(K, V, null, FNV!(64, true)); 1792 1793 auto s = X.withCapacity(n); 1794 1795 void dummy(ref V value) {} 1796 1797 assertThrown!RangeError(dummy(s[K.init])); 1798 1799 foreach (immutable uint i; 0 .. n) 1800 { 1801 s[i] = new V(i); 1802 assertNotThrown!RangeError(dummy(s[i])); 1803 } 1804 1805 // test range 1806 auto sr = s.byKeyValue; 1807 assert(sr.length == n); 1808 foreach (immutable uint i; 0 .. n) 1809 { 1810 sr.popFront(); 1811 assert(sr.length == n - i - 1); 1812 } 1813 1814 foreach (immutable uint i; 0 .. n) 1815 { 1816 s.remove(i); 1817 assertThrown!RangeError(dummy(s[i])); 1818 } 1819 1820 s[K.init] = V.init; 1821 auto vp = K.init in s; 1822 static assert(is(typeof(vp) == V*)); 1823 1824 s.remove(K.init); 1825 assert(K.init !in s); 1826 1827 X t; 1828 t.reserveExtra(4096); 1829 assert(t.binCount == 8192); 1830 } 1831 1832 /// constness inference of ranges 1833 pure nothrow unittest 1834 { 1835 alias K = uint; 1836 class V 1837 { 1838 this(uint data) { this.data = data; } 1839 uint data; 1840 } 1841 1842 alias X = SSOHashMapOrSet!(K, V, null, FNV!(64, true)); 1843 const x = X(); 1844 1845 foreach (e; x.byKey) 1846 { 1847 static assert(is(typeof(e) == const(X.KeyType))); 1848 } 1849 1850 foreach (e; x.byValue) 1851 { 1852 static assert(is(typeof(e) == X.ValueType)); // TODO: should be const(X.ValueType) 1853 } 1854 1855 foreach (e; x.byKeyValue) 1856 { 1857 static assert(is(typeof(e.key) == const(X.KeyType))); 1858 static assert(is(typeof(e.value) == const(X.ValueType))); 1859 static assert(is(typeof(e) == const(X.ElementType))); 1860 } 1861 } 1862 1863 /// range key constness and value mutability with `class` value 1864 pure nothrow unittest 1865 { 1866 struct K 1867 { 1868 @disable this(this); 1869 uint value; 1870 } 1871 1872 class V 1873 { 1874 this(uint data) { this.data = data; } 1875 uint data; 1876 } 1877 1878 alias X = SSOHashMapOrSet!(K, V, null, FNV!(64, true)); 1879 auto x = X(); 1880 1881 x[K(42)] = new V(43); 1882 1883 assert(x.length == 1); 1884 1885 foreach (e; x.byValue) // `e` is auto ref 1886 { 1887 static assert(is(typeof(e) == X.ValueType)); // mutable access to value 1888 assert(e.data == 43); 1889 1890 // value mutation side effects 1891 e.data += 1; 1892 assert(e.data == 44); 1893 e.data -= 1; 1894 assert(e.data == 43); 1895 } 1896 1897 foreach (ref e; x.byKeyValue) // `e` is auto ref 1898 { 1899 static assert(is(typeof(e.key) == const(X.KeyType))); // const access to key 1900 1901 static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value 1902 1903 assert(e.value.data == 43); 1904 1905 // value mutation side effects 1906 e.value.data += 1; 1907 assert(e.value.data == 44); 1908 e.value.data -= 1; 1909 assert(e.value.data == 43); 1910 } 1911 } 1912 1913 version(unittest) 1914 { 1915 import std.algorithm.comparison : equal; 1916 import nxt.digestx.fnv : FNV; 1917 import nxt.array_help : s; 1918 }