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 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 { 457 insert(element); 458 } 459 else 460 { 461 insert(*cast(Unqual!T*)&element); 462 } 463 } 464 } 465 466 /** Reserve rom for `extraCapacity` number of extra buckets. */ 467 pragma(inline, true) 468 void reserveExtra()(size_t extraCapacity) 469 { 470 if ((capacityScaleNumerator * 471 (_length + extraCapacity) / 472 capacityScaleDenominator) > 473 _bins.length * smallBinCapacity) 474 { 475 growWithExtraCapacity(extraCapacity); 476 } 477 } 478 479 static if (hasValue) 480 { 481 /** Insert or replace `value` at `key`. */ 482 pragma(inline, true) // LDC must have this 483 InsertionStatus insert(K key, V value) 484 { 485 return insert(T(move(key), 486 move(value))); 487 } 488 } 489 490 static private size_t offsetOfKey(in T[] elements, 491 in ref K key) 492 { 493 version(LDC) pragma(inline, true); 494 size_t elementOffset = 0; 495 foreach (const ref e; elements) 496 { 497 if (keyOf(e) is key) { break; } 498 elementOffset += 1; 499 } 500 return elementOffset; 501 } 502 503 pragma(inline, true) 504 static private bool hasKey(in T[] elements, 505 in ref K key) 506 { 507 return offsetOfKey(elements, key) != elements.length; 508 } 509 510 /** Insert `element` like with `insert()` without automatic growth of number 511 * of bins. 512 */ 513 InsertionStatus insertMoveWithoutBinCountGrowth(ref T element) @trusted // ref simplifies move 514 { 515 immutable binIx = keyToBinIx(keyRefOf(element)); 516 T[] elements = binElementsAt(binIx); 517 immutable elementOffset = offsetOfKey(elements, keyOf(element)); 518 immutable elementFound = elementOffset != elements.length; 519 520 if (elementFound) 521 { 522 static if (hasValue) 523 { 524 /* TODO Rust does the same in its `insert()` at 525 * https://doc.rust-lang.org/std/collections/struct.HashMap.html 526 */ 527 if (elements[elementOffset].value !is valueOf(element)) // if different value (or identity for classes) 528 { 529 // replace value 530 static if (needsMove!V) 531 { 532 move(valueOf(element), 533 elements[elementOffset].value); 534 } 535 else 536 { 537 elements[elementOffset].value = valueOf(element); 538 } 539 return typeof(return).modified; 540 } 541 } 542 return typeof(return).unmodified; 543 } 544 else // no elementFound 545 { 546 if (_bstates[binIx].isLarge) // stay large 547 { 548 _bins[binIx].large.insertBackMove(element); 549 } 550 else 551 { 552 if (_bstates[binIx].isFullSmall) // expand small to large 553 { 554 static if (needsMove!T) 555 { 556 // TODO functionize to concatenation:moveConcatenate() 557 T[smallBinCapacity + 1] smallCopy = void; 558 moveEmplaceAllNoReset(_bins[binIx].small[], 559 smallCopy[0 .. smallBinCapacity]); 560 moveEmplace(element, 561 smallCopy[smallBinCapacity]); 562 563 LargeBin.emplaceWithMovedElements(&_bins[binIx].large, 564 smallCopy[]); 565 } 566 else 567 { 568 import nxt.concatenation : concatenate; 569 auto smallCopy = concatenate(_bins[binIx].small, element); 570 emplace!(LargeBin)(&_bins[binIx].large, smallCopy); 571 } 572 _bstates[binIx].makeLarge(); 573 } 574 else // stay small 575 { 576 static if (needsMove!T) 577 { 578 moveEmplace(element, 579 _bins[binIx].small[_bstates[binIx].smallCount]); 580 } 581 else 582 { 583 _bins[binIx].small[_bstates[binIx].smallCount] = element; 584 } 585 _bstates[binIx].incSmallCount(); 586 } 587 } 588 _length += 1; 589 return typeof(return).added; 590 } 591 } 592 593 /** L-value element reference (and in turn range iterator). 594 */ 595 static private struct LvalueElementRef(SomeHashMapOrSet) 596 { 597 SomeHashMapOrSet* table; 598 size_t binIx; // index to bin inside table 599 size_t elementOffset; // offset to element inside bin 600 size_t elementCounter; // counter over number of elements popped 601 602 pragma(inline, true): 603 604 /// Check if empty. 605 @property bool empty() const @safe pure nothrow @nogc 606 { 607 return binIx == table.binCount; 608 } 609 610 /// Get number of element left to pop. 611 @property size_t length() const @safe pure nothrow @nogc 612 { 613 return table.length - elementCounter; 614 } 615 616 void initFirstNonEmptyBin() 617 { 618 while (binIx < table.binCount && 619 table.binElementCountAt(binIx) == 0) 620 { 621 binIx += 1; 622 } 623 } 624 625 pragma(inline) 626 void popFront() 627 { 628 assert(!empty); 629 elementOffset += 1; // next element 630 // if current bin was emptied 631 while (elementOffset >= table.binElementsAt(binIx).length) 632 { 633 // next bin 634 binIx += 1; 635 elementOffset = 0; 636 if (empty) { break; } 637 } 638 elementCounter += 1; 639 } 640 641 @property typeof(this) save() // ForwardRange 642 { 643 return this; 644 } 645 } 646 647 /** R-value element reference (and in turn range iterator). 648 */ 649 static private struct RvalueElementRef(SomeHashMapOrSet) 650 { 651 SomeHashMapOrSet table; // owned 652 size_t binIx; // index to bin inside table 653 size_t elementOffset; // offset to element inside bin 654 size_t elementCounter; // counter over number of elements popped 655 656 pragma(inline, true): 657 658 /// Check if empty. 659 @property bool empty() const @safe pure nothrow @nogc 660 { 661 return binIx == table.binCount; 662 } 663 664 /// Get number of element left to pop. 665 @property size_t length() const @safe pure nothrow @nogc 666 { 667 return table.length - elementCounter; 668 } 669 670 void initFirstNonEmptyBin() 671 { 672 while (binIx < table.binCount && 673 table.binElementCountAt(binIx) == 0) 674 { 675 binIx += 1; 676 } 677 } 678 679 pragma(inline) 680 void popFront() 681 { 682 assert(!empty); 683 elementOffset += 1; // next element 684 // if current bin was emptied 685 while (elementOffset >= table.binElementsAt(binIx).length) 686 { 687 // next bin 688 binIx += 1; 689 elementOffset = 0; 690 if (empty) { break; } 691 } 692 elementCounter += 1; 693 } 694 } 695 696 static if (!hasValue) // HashSet 697 { 698 pragma(inline, true) 699 bool opBinaryRight(string op)(in K key) inout @trusted 700 if (op == "in") 701 { 702 return contains(key); 703 } 704 705 /// Range over elements of l-value instance of this. 706 static private struct ByLvalueElement(SomeHashMapOrSet) 707 { 708 pragma(inline, true): 709 static if (is(ElementType == class)) 710 { 711 /// Get reference to front element (key and value). 712 @property scope auto front()() return @trusted 713 { 714 /* cast away const from `SomeHashMapOrSet` for classes 715 * because class elements are currently hashed and compared 716 * compared using their identity (pointer value) `is` 717 */ 718 return cast(ElementType)table.binElementsAt(binIx)[elementOffset]; 719 } 720 } 721 else 722 { 723 /// Get reference to front element (key and value). 724 @property scope auto front()() 725 { 726 return table.binElementsAt(binIx)[elementOffset]; 727 } 728 } 729 public LvalueElementRef!SomeHashMapOrSet _elementRef; 730 alias _elementRef this; 731 } 732 733 /// Range over elements of r-value instance of this. 734 static private struct ByRvalueElement(SomeHashMapOrSet) 735 { 736 pragma(inline, true): 737 static if (is(ElementType == class)) 738 { 739 /// Get reference to front element (key and value). 740 @property scope auto front()() return @trusted 741 { 742 /* cast away const from `SomeHashMapOrSet` for classes 743 * because class elements are currently hashed and compared 744 * compared using their identity (pointer value) `is` 745 */ 746 return cast(ElementType)table.binElementsAt(binIx)[elementOffset]; 747 } 748 } 749 else 750 { 751 /// Get reference to front element (key and value). 752 @property scope auto front()() 753 { 754 return table.binElementsAt(binIx)[elementOffset]; 755 } 756 } 757 public RvalueElementRef!SomeHashMapOrSet _elementRef; 758 alias _elementRef this; 759 } 760 761 /// ditto 762 version(none) // cannot be combined 763 { 764 pragma(inline, true) 765 scope auto opSlice()() inout return // template-lazy 766 { 767 return byElement(); 768 } 769 } 770 } 771 772 static if (hasValue) // HashMap 773 { 774 scope inout(V)* opBinaryRight(string op)(in K key) inout @trusted return 775 if (op == "in") 776 { 777 if (empty) 778 { 779 // prevent range error in `binElementsAt` when `this` is empty 780 return typeof(return).init; 781 } 782 immutable binIx = keyToBinIx(key); 783 const elements = binElementsAt(binIx); 784 immutable elementOffset = offsetOfKey(elements, key); 785 immutable elementFound = elementOffset != elements.length; 786 if (elementFound) 787 { 788 return cast(typeof(return))&elements[elementOffset].value; 789 // return typeof(return)(&this, binIx, elementOffset); 790 } 791 else // miss 792 { 793 return typeof(return).init; 794 } 795 } 796 797 static private struct ByKey(SomeHashMapOrSet) 798 { 799 pragma(inline, true): 800 /// Get reference to key of front element. 801 @property const scope auto ref front()() return // key access must be const 802 { 803 return table.binElementsAt(binIx)[elementOffset].key; 804 } 805 public LvalueElementRef!SomeHashMapOrSet _elementRef; 806 alias _elementRef this; 807 } 808 809 /// Returns forward range that iterates through the keys of `this`. 810 @property scope auto byKey()() inout return @trusted // template-lazy property 811 { 812 alias This = ConstThis; 813 auto result = ByKey!This((LvalueElementRef!This(cast(This*)&this))); 814 result.initFirstNonEmptyBin(); 815 return result; 816 } 817 818 static private struct ByValue(SomeHashMapOrSet) 819 { 820 pragma(inline, true): 821 /// Get reference to value of front element. 822 @property scope auto ref front()() return @trusted // template-lazy property. TODO remove @trusted 823 { 824 return *(cast(ValueType*)(&table.binElementsAt(binIx)[elementOffset].value)); // TODO remove reinterpret cast 825 } 826 public LvalueElementRef!SomeHashMapOrSet _elementRef; 827 alias _elementRef this; 828 } 829 830 /// Returns forward range that iterates through the values of `this`. 831 @property scope auto byValue()() inout return @trusted // template-lazy property 832 { 833 alias This = ConstThis; 834 auto result = ByValue!This((LvalueElementRef!This(cast(This*)&this))); 835 result.initFirstNonEmptyBin(); 836 return result; 837 } 838 839 static private struct ByKeyValue(SomeHashMapOrSet) 840 { 841 pragma(inline, true): 842 /// Get reference to front element (key and value). 843 @property scope auto ref front()() return @trusted 844 { 845 static if (isMutable!(SomeHashMapOrSet)) 846 { 847 alias E = CT; 848 } 849 else 850 { 851 alias E = const(T); 852 } 853 return *(cast(E*)&table.binElementsAt(binIx)[elementOffset]); // TODO remove cast 854 } 855 public LvalueElementRef!SomeHashMapOrSet _elementRef; 856 alias _elementRef this; 857 } 858 859 /// Returns forward range that iterates through the keys and values of `this`. 860 @property scope auto byKeyValue()() return @trusted // template-lazy property 861 { 862 alias This = MutableThis; 863 auto result = ByKeyValue!This((LvalueElementRef!This(cast(This*)&this))); 864 result.initFirstNonEmptyBin(); 865 return result; 866 } 867 /// ditto 868 @property scope auto byKeyValue()() const return @trusted // template-lazy property 869 { 870 alias This = ConstThis; 871 auto result = ByKeyValue!This((LvalueElementRef!This(cast(This*)&this))); 872 result.initFirstNonEmptyBin(); 873 return result; 874 } 875 876 /// ditto 877 pragma(inline, true) 878 scope auto opSlice()() return // template-lazy 879 { 880 return byKeyValue(); 881 } 882 883 /// Indexing. 884 scope ref inout(V) opIndex()(in K key) inout return // auto ref here makes things slow 885 { 886 version(LDC) pragma(inline, true); 887 immutable binIx = keyToBinIx(key); 888 auto elements = binElementsAt(binIx); 889 890 immutable elementOffset = offsetOfKey(elements, key); 891 immutable elementFound = elementOffset != elements.length; 892 if (elementFound) 893 { 894 return binElementsAt(binIx)[elementOffset].value; 895 } 896 version(assert) 897 { 898 import core.exception : RangeError; 899 throw new RangeError("Key not found"); 900 } 901 } 902 903 /** Get value of `key` or `defaultValue` if `key` not present (and 904 * therefore `nothrow`). 905 * 906 * Returns: value reference iff `defaultValue` is an l-value. 907 * 908 * TODO make `defaultValue` `lazy` when that can be `nothrow` 909 */ 910 auto ref V get()(in K key, in V defaultValue) @trusted // auto ref here makes things slow 911 { 912 import std.algorithm.searching : countUntil; 913 immutable binIx = keyToBinIx(key); 914 immutable ptrdiff_t elementOffset = binElementsAt(binIx).countUntil!(_ => _.key is key); // TODO functionize 915 if (elementOffset != -1) // elementFound 916 { 917 return binElementsAt(binIx)[elementOffset].value; 918 } 919 else // miss 920 { 921 return defaultValue; 922 } 923 } 924 925 /** Supports $(B aa[key] = value;) syntax. 926 */ 927 void opIndexAssign()(V value, K key) // template-lazy 928 { 929 version(LDC) pragma(inline, true); 930 insert(T(move(key), 931 move(value))); 932 // TODO return reference to value 933 } 934 935 static if (__traits(compiles, { V _; _ += 1; })) // if we can increase the key 936 { 937 /** Increase value at `key`, or set value to 1 if `key` hasn't yet 938 * been added. 939 */ 940 pragma(inline, true) 941 void autoinitIncAt()(in K key) // template-lazy 942 { 943 auto elementFound = key in this; 944 if (elementFound) 945 { 946 (*elementFound) += 1; 947 } 948 else 949 { 950 insert(key, V.init + 1); 951 } 952 } 953 } 954 955 } 956 957 /** Remove `element` and, when possible, shrink its large bin to small. 958 959 Returns: `true` if element was removed, `false` otherwise. 960 */ 961 bool remove()(in K key) // template-lazy 962 @trusted 963 { 964 immutable binIx = keyToBinIx(key); 965 import nxt.container_algorithm : popFirstMaybe; 966 if (_bstates[binIx].isLarge) 967 { 968 immutable elementFound = _bins[binIx].large.popFirstMaybe!keyEqualPred(key); 969 _length -= elementFound ? 1 : 0; 970 if (elementFound) 971 { 972 tryShrinkLargeBinAt(binIx); 973 } 974 return elementFound; 975 } 976 else 977 { 978 const elements = smallBinElementsAt(binIx); 979 immutable elementIx = offsetOfKey(elements, key); 980 immutable elementFound = elementIx != elements.length; 981 if (elementFound) 982 { 983 removeSmallElementAt(binIx, elementIx); 984 } 985 _length -= elementFound ? 1 : 0; 986 return elementFound; 987 } 988 } 989 990 /** Remove small element at `elementIx` in bin `binIx`. */ 991 private void removeSmallElementAt()(size_t binIx, // template-lazy 992 size_t elementIx) 993 { 994 assert(!_bstates[binIx].isLarge); 995 import nxt.container_algorithm : shiftToFrontAt; 996 smallBinElementsAt(binIx).shiftToFrontAt(elementIx); 997 _bstates[binIx].decSmallCount(); 998 static if (hasElaborateDestructor!T) 999 { 1000 .destroy(_bins[binIx].small[_bstates[binIx].smallCount]); // TODO this is incorrect 1001 } 1002 } 1003 1004 /** Shrink large bin at `binIx` possible posbiel. */ 1005 private void tryShrinkLargeBinAt()(size_t binIx) // template-lazy 1006 { 1007 assert(_bstates[binIx].isLarge); 1008 immutable count = _bins[binIx].large.length; 1009 if (count <= smallBinCapacity) // large fits in small 1010 { 1011 SmallBin smallCopy = void; 1012 moveEmplaceAllNoReset(_bins[binIx].large[0 .. count], 1013 smallCopy[0 .. count]); 1014 static if (hasElaborateDestructor!LargeBin) 1015 { 1016 .destroy(_bins[binIx].large); 1017 } 1018 moveEmplaceAllNoReset(smallCopy[0 .. count], 1019 _bins[binIx].small[0 .. count]); 1020 _bstates[binIx].smallCount = count; 1021 } 1022 } 1023 1024 /** Rehash. 1025 * 1026 * Reorganize `this` in place so that lookups are more efficient. 1027 */ 1028 ref typeof(this) rehash()() @trusted // template-lazy 1029 { 1030 static assert(0, "TODO remove template parens of this functions and implement"); 1031 // return this; 1032 } 1033 1034 /// Check if empty. 1035 pragma(inline, true) 1036 @property bool empty() const { return _length == 0; } 1037 1038 /// Get length (read-only). 1039 pragma(inline, true) 1040 @property size_t length() const { return _length; } 1041 1042 /// Get bin count. 1043 pragma(inline, true) 1044 @property size_t binCount() const { return _bins.length; } 1045 1046 /// Bin count statistics. 1047 struct BinCounts 1048 { 1049 size_t smallCount; // number of hybrid bins being small 1050 size_t largeCount; // number of hybrid bins being large 1051 } 1052 1053 /// Returns: bin count statistics for small and large bins. 1054 pragma(inline, false) 1055 BinCounts binCounts()() const // template-lazy 1056 { 1057 import std.algorithm : count; 1058 immutable largeCount = _bstates[].count!(_ => _.isLarge); 1059 immutable smallCount = _bstates.length - largeCount; 1060 auto result = typeof(return)(smallCount, 1061 largeCount); 1062 assert(result.largeCount + result.smallCount == _bstates.length); 1063 return result; 1064 } 1065 1066 /** Returns: elements in bin at `binIx`. */ 1067 pragma(inline, true) 1068 private scope inout(T)[] binElementsAt(size_t binIx) inout return @trusted 1069 { 1070 if (_bstates[binIx].isLarge) 1071 { 1072 return _bins[binIx].large[]; 1073 } 1074 else 1075 { 1076 return smallBinElementsAt(binIx); 1077 } 1078 } 1079 1080 pragma(inline, true) 1081 private scope inout(T)[] smallBinElementsAt(size_t binIx) inout return 1082 { 1083 return _bins[binIx].small[0 .. _bstates[binIx].smallCount]; 1084 } 1085 1086 /** Returns: number of elements in bin at `binIx`. */ 1087 pragma(inline, true) 1088 private size_t binElementCountAt(size_t binIx) const @trusted 1089 { 1090 if (_bstates[binIx].isLarge) 1091 { 1092 return _bins[binIx].large.length; 1093 } 1094 else 1095 { 1096 return _bstates[binIx].smallCount; 1097 } 1098 } 1099 1100 /** Maximum number of elements that fits in a small bin 1101 * (`SmallBin`). 1102 */ 1103 enum smallBinCapacity = max(smallBinMinCapacity, 1104 LargeBin.sizeof / T.sizeof); 1105 1106 private: 1107 import nxt.dynamic_array : Array = DynamicArray; 1108 1109 /** 32-bit capacity and length for LargeBinLnegth on 64-bit platforms 1110 * saves one word and makes insert() and contains() significantly faster */ 1111 alias LargeBinCapacityType = uint; 1112 alias LargeBin = Array!(T, Allocator, LargeBinCapacityType); 1113 1114 alias SmallBin = T[smallBinCapacity]; 1115 1116 /// no space for `SmallBin`s should be wasted 1117 static assert(SmallBin.sizeof >= LargeBin.sizeof); 1118 1119 /** Small-size-optimized bin. 1120 */ 1121 union HybridBin 1122 { 1123 SmallBin small; 1124 LargeBin large; 1125 } 1126 1127 static if (mustAddGCRange!LargeBin) 1128 { 1129 static assert(mustAddGCRange!HybridBin, "HybridBin mustAddGCRange when LargeBin is " ~ LargeBin.stringof); 1130 } 1131 static if (mustAddGCRange!SmallBin) 1132 { 1133 static assert(mustAddGCRange!HybridBin, "HybridBin mustAddGCRange when SmallBin is " ~ SmallBin.stringof); 1134 } 1135 1136 /** Count and large status of bin. */ 1137 struct Bstate 1138 { 1139 alias Count = ubyte; 1140 1141 pragma(inline, true): 1142 1143 @property Count smallCount() const 1144 { 1145 assert(_count <= smallBinCapacity); 1146 return _count; 1147 } 1148 1149 @property void smallCount(size_t count) 1150 { 1151 assert(count <= smallBinCapacity); 1152 _count = cast(Count)count; 1153 } 1154 1155 void decSmallCount() 1156 { 1157 assert(_count >= 1); 1158 _count -= 1; 1159 } 1160 1161 void incSmallCount() 1162 { 1163 assert(_count + 1 <= smallBinCapacity); 1164 _count += 1; 1165 } 1166 1167 /** Returns: `true` iff `this` is a large bin. */ 1168 @property bool isLarge() const 1169 { 1170 return _count == Count.max; 1171 } 1172 1173 void makeLarge() 1174 { 1175 _count = Count.max; 1176 } 1177 1178 /** Returns: `true` iff `this` is a full small bin. */ 1179 @property bool isFullSmall() const 1180 { 1181 return _count == smallBinCapacity; 1182 } 1183 1184 Count _count; 1185 } 1186 1187 /** Small-size-optimized bin array. 1188 Size-state (including small or large) for each element is determined by 1189 corresponding element in `Bstates`. 1190 */ 1191 alias Bins = Array!(HybridBin, Allocator); 1192 1193 /// Bin states. 1194 alias Bstates = Array!(Bstate, Allocator); 1195 1196 // TODO merge these into an instance of soa.d and remove invariant 1197 Bins _bins; // bin elements 1198 Bstates _bstates; // bin states 1199 invariant 1200 { 1201 assert(_bins.length == 1202 _bstates.length); 1203 } 1204 1205 size_t _length; // total number of elements stored 1206 1207 /** Returns: bin index of `hash`. */ 1208 pragma(inline, true) 1209 size_t hashToIndex(hash_t hash) const 1210 { 1211 return hash & powerOf2Mask; 1212 } 1213 1214 /** Returns: bin index of `key`. */ 1215 size_t keyToBinIx()(in auto ref K key) const 1216 { 1217 version(LDC) pragma(inline, true); 1218 import nxt.digestion : hashOf2; 1219 return hashToIndex(hashOf2!(hasher)(key)); 1220 } 1221 1222 /** Returns: current index mask from bin count. */ 1223 pragma(inline, true) 1224 private size_t powerOf2Mask() const 1225 { 1226 immutable typeof(return) mask = _bins.length - 1; 1227 assert((~mask ^ mask) == size_t.max); // isPowerOf2(_bins.length) 1228 return mask; 1229 } 1230 } 1231 1232 /** Hash map storing keys of type `K` and values of type `V`. 1233 */ 1234 alias SSOHashMap(K, V, 1235 alias Allocator = null, 1236 alias hasher = hashOf, 1237 uint smallBinMinCapacity = 1) = SSOHashMapOrSet!(K, V, 1238 Allocator, 1239 hasher, 1240 smallBinMinCapacity); 1241 1242 /** Hash map storing keys of type `K`. 1243 */ 1244 alias SSOHashSet(K, 1245 alias Allocator = null, 1246 alias hasher = hashOf, 1247 uint smallBinMinCapacity = 1) = SSOHashMapOrSet!(K, void, 1248 Allocator, 1249 hasher, 1250 smallBinMinCapacity); 1251 1252 import std.traits : isInstanceOf; 1253 import std.functional : unaryFun; 1254 1255 /** Remove all elements in `x` matching `predicate`. 1256 TODO move to container_algorithm.d. 1257 */ 1258 void removeAllMatching(alias predicate, SomeHashMapOrSet)(auto ref SomeHashMapOrSet x) 1259 @trusted 1260 if (isInstanceOf!(SSOHashMapOrSet, 1261 SomeHashMapOrSet)) 1262 { 1263 import core.lifetime : moveEmplace; 1264 foreach (immutable binIx; 0 .. x._bins.length) 1265 { 1266 if (x._bstates[binIx].isLarge) 1267 { 1268 import nxt.dynamic_array : remove; 1269 immutable removeCount = x._bins[binIx].large.remove!predicate(); 1270 x._length -= removeCount; 1271 x.tryShrinkLargeBinAt(binIx); 1272 } 1273 else 1274 { 1275 SomeHashMapOrSet.SmallBin tmpSmall; 1276 SomeHashMapOrSet.Bstate tmpBstate; 1277 foreach (ref element; x.smallBinElementsAt(binIx)) 1278 { 1279 if (unaryFun!predicate(element)) 1280 { 1281 import core.internal.traits : hasElaborateDestructor; 1282 static if (hasElaborateDestructor!(SomeHashMapOrSet.T)) 1283 { 1284 destroy(element); 1285 } 1286 x._length -= 1; 1287 } 1288 else 1289 { 1290 moveEmplace(element, tmpSmall[tmpBstate.smallCount()]); 1291 tmpBstate.incSmallCount(); 1292 } 1293 } 1294 assert(!tmpBstate.isLarge); // should stay small 1295 moveEmplace(tmpSmall, x._bins[binIx].small); 1296 moveEmplace(tmpBstate, x._bstates[binIx]); 1297 } 1298 } 1299 } 1300 1301 /** Returns: `x` eagerly filtered on `predicate`. 1302 TODO move to container_algorithm.d. 1303 */ 1304 SomeHashMapOrSet filtered(alias predicate, SomeHashMapOrSet)(SomeHashMapOrSet x) 1305 @trusted 1306 if (isInstanceOf!(SSOHashMapOrSet, 1307 SomeHashMapOrSet)) 1308 { 1309 import std.functional : not; 1310 removeAllMatching!(not!predicate)(x); 1311 import std.algorithm.mutation : move; 1312 return move(x); 1313 } 1314 1315 /** Returns: `x` eagerly intersected with `y`. 1316 TODO move to container_algorithm.d. 1317 */ 1318 auto intersectedWith(C1, C2)(C1 x, auto ref C2 y) 1319 if (isInstanceOf!(SSOHashMapOrSet, C1) && 1320 isInstanceOf!(SSOHashMapOrSet, C2)) 1321 { 1322 import std.algorithm.mutation : move; 1323 static if (__traits(isRef, y)) // y is l-value 1324 { 1325 // @("complexity", "O(x.length)") 1326 return move(x).filtered!(_ => y.contains(_)); // only x can be reused 1327 } 1328 else 1329 { 1330 /* both are r-values so reuse the shortest */ 1331 // @("complexity", "O(min(x.length), min(y.length))") 1332 if (x.length < 1333 y.length) 1334 { 1335 return move(x).filtered!(_ => y.contains(_)); 1336 } 1337 else 1338 { 1339 return move(y).filtered!(_ => x.contains(_)); 1340 } 1341 } 1342 } 1343 1344 /// r-value and l-value intersection 1345 @safe pure nothrow @nogc unittest 1346 { 1347 alias K = uint; 1348 alias X = SSOHashMapOrSet!(K, void, null, FNV!(64, true)); 1349 1350 auto x = X.withElements([12, 13].s); 1351 auto y = X.withElements([10, 12, 13, 15].s).intersectedWith(x); 1352 assert(y.length == 2); 1353 assert(y.contains(12)); 1354 assert(y.contains(13)); 1355 } 1356 1357 /// r-value and r-value intersection 1358 @safe pure nothrow @nogc unittest 1359 { 1360 alias K = uint; 1361 alias X = SSOHashMapOrSet!(K, void, null, FNV!(64, true)); 1362 1363 auto y = X.withElements([10, 12, 13, 15].s).intersectedWith(X.withElements([12, 13].s)); 1364 assert(y.length == 2); 1365 assert(y.contains(12)); 1366 assert(y.contains(13)); 1367 } 1368 1369 /** Returns: `x` eagerly intersected with `y`. 1370 TODO move to container_algorithm.d. 1371 */ 1372 auto intersectWith(C1, C2)(ref C1 x, 1373 auto ref const(C2) y) 1374 if (isInstanceOf!(SSOHashMapOrSet, C1) && 1375 isInstanceOf!(SSOHashMapOrSet, C2)) 1376 { 1377 return x.removeAllMatching!(_ => !y.contains(_)); 1378 } 1379 1380 /// r-value and l-value intersection 1381 @safe pure nothrow @nogc unittest 1382 { 1383 alias K = uint; 1384 alias X = SSOHashMapOrSet!(K, void, null, FNV!(64, true)); 1385 1386 auto x = X.withElements([12, 13].s); 1387 auto y = X.withElements([10, 12, 13, 15].s); 1388 y.intersectWith(x); 1389 assert(y.length == 2); 1390 assert(y.contains(12)); 1391 assert(y.contains(13)); 1392 } 1393 1394 /// Returns forward range that iterates through the elements of `c`. 1395 auto byElement(SomeHashMapOrSet)(auto ref inout(SomeHashMapOrSet) c) 1396 @trusted 1397 if (isInstanceOf!(SSOHashMapOrSet, 1398 SomeHashMapOrSet)) 1399 { 1400 alias C = const(SomeHashMapOrSet); 1401 static if (__traits(isRef, c)) 1402 { 1403 auto result = C.ByLvalueElement!C((C.LvalueElementRef!C(cast(C*)&c))); 1404 result.initFirstNonEmptyBin(); 1405 return result; 1406 } 1407 else 1408 { 1409 import std.algorithm.mutation : move; 1410 auto result = C.ByRvalueElement!C((C.RvalueElementRef!C(move(*(cast(SomeHashMapOrSet*)&c))))); // reinterpret 1411 result.initFirstNonEmptyBin(); 1412 return move(result); 1413 } 1414 } 1415 alias range = byElement; // EMSI-container naming 1416 1417 @safe: 1418 1419 /// make range from l-value and r-value. element access is always const 1420 pure nothrow @nogc unittest 1421 { 1422 alias K = uint; 1423 alias X = SSOHashMapOrSet!(K, void, null, FNV!(64, true)); 1424 1425 immutable a = [11, 22].s; 1426 1427 // mutable 1428 auto x = X.withElements(a); 1429 foreach (e; x.byElement) // from l-value 1430 { 1431 static assert(is(typeof(e) == const(K))); // always const access 1432 } 1433 1434 // const 1435 const y = X.withElements(a); 1436 foreach (e; y.byElement) // from l-value 1437 { 1438 static assert(is(typeof(e) == const(K))); 1439 } 1440 1441 foreach (e; X.withElements([11].s).byElement) // from r-value 1442 { 1443 static assert(is(typeof(e) == const(K))); // always const access 1444 } 1445 } 1446 1447 /// test various things 1448 pure nothrow @nogc unittest 1449 { 1450 immutable n = 600; 1451 1452 alias K = uint; 1453 1454 import std.meta : AliasSeq; 1455 foreach (V; AliasSeq!(void, string)) 1456 { 1457 alias X = SSOHashMapOrSet!(K, V, null, FNV!(64, true)); 1458 1459 static if (!X.hasValue) 1460 { 1461 auto x = X.withElements([11, 12, 13].s); 1462 1463 import std.algorithm : count; 1464 auto xr = x.byElement; 1465 1466 alias R = typeof(xr); 1467 import std.range.primitives : isInputRange; 1468 import std.traits : ReturnType; 1469 static assert(is(typeof(R.init) == R)); 1470 static assert(is(ReturnType!((R xr) => xr.empty) == bool)); 1471 auto f = xr.front; 1472 static assert(is(typeof((R xr) => xr.front))); 1473 static assert(!is(ReturnType!((R xr) => xr.front) == void)); 1474 static assert(is(typeof((R xr) => xr.popFront))); 1475 1476 static assert(isInputRange!(typeof(xr))); 1477 1478 assert(x.byElement.count == 3); 1479 1480 X y; 1481 foreach (const ref e; x.byElement) 1482 { 1483 y.insert(e); 1484 } 1485 1486 assert(y.byElement.count == 3); 1487 assert(x == y); 1488 1489 const z = X(); 1490 assert(z.byElement.count == 0); 1491 1492 immutable w = X(); 1493 assert(w.byElement.count == 0); 1494 1495 { 1496 auto xc = X.withElements([11, 12, 13].s); 1497 assert(xc.length == 3); 1498 assert(xc.contains(11)); 1499 1500 // TODO http://forum.dlang.org/post/kvwrktmameivubnaifdx@forum.dlang.org 1501 xc.removeAllMatching!(_ => _ == 11); 1502 1503 assert(xc.length == 2); 1504 assert(!xc.contains(11)); 1505 1506 xc.removeAllMatching!(_ => _ == 12); 1507 assert(!xc.contains(12)); 1508 assert(xc.length == 1); 1509 1510 xc.removeAllMatching!(_ => _ == 13); 1511 assert(!xc.contains(13)); 1512 assert(xc.length == 0); 1513 1514 // this is ok 1515 foreach (e; xc.byElement) {} 1516 1517 } 1518 1519 { 1520 auto k = X.withElements([11, 12].s).filtered!(_ => _ != 11).byElement; 1521 static assert(isInputRange!(typeof(k))); 1522 assert(k.front == 12); 1523 k.popFront(); 1524 assert(k.empty); 1525 } 1526 1527 { 1528 X q; 1529 auto qv = [11U, 12U, 13U, 14U].s; 1530 q.insertN(qv); 1531 foreach (e; qv[]) 1532 { 1533 assert(q.contains(e)); 1534 } 1535 q.clear(); 1536 assert(q.empty); 1537 } 1538 } 1539 1540 import nxt.container_traits : mustAddGCRange; 1541 static if (X.hasValue && 1542 is(V == string)) 1543 { 1544 static assert(mustAddGCRange!V); 1545 static assert(mustAddGCRange!(V[1])); 1546 static assert(mustAddGCRange!(X.T)); 1547 static assert(mustAddGCRange!(X.SmallBin)); 1548 static assert(!mustAddGCRange!(X.LargeBin)); 1549 } 1550 else 1551 { 1552 static assert(!mustAddGCRange!(X.T)); 1553 static assert(!mustAddGCRange!(X.SmallBin), "Fails for X being " ~ X.SmallBin.stringof); 1554 } 1555 1556 auto x1 = X(); // start empty 1557 1558 // all bins start small 1559 assert(x1.binCounts.largeCount == 0); 1560 1561 // fill x1 1562 1563 foreach (immutable key; 0 .. n) 1564 { 1565 static if (X.hasValue) 1566 { 1567 const value = V.init; 1568 const element = X.ElementType(key, value); 1569 } 1570 else 1571 { 1572 const element = key; 1573 } 1574 1575 assert(key !in x1); 1576 1577 assert(x1.length == key); 1578 assert(x1.insert(element) == X.InsertionStatus.added); 1579 1580 static if (X.hasValue) 1581 { 1582 const e2 = X.ElementType(key, "a"); 1583 assert(x1.insert(e2) == X.InsertionStatus.modified); 1584 assert(x1.contains(key)); 1585 assert(x1.get(key, null) == "a"); 1586 x1.remove(key); 1587 x1[key] = value; 1588 } 1589 1590 assert(x1.length == key + 1); 1591 1592 assert(key in x1); 1593 static if (X.hasValue) 1594 { 1595 auto elementFound = key in x1; 1596 assert(elementFound); 1597 assert(*elementFound != "_"); 1598 } 1599 1600 assert(x1.insert(element) == X.InsertionStatus.unmodified); 1601 static if (X.hasValue) 1602 { 1603 assert(x1.insert(key, value) == X.InsertionStatus.unmodified); 1604 } 1605 assert(x1.length == key + 1); 1606 1607 assert(key in x1); 1608 } 1609 1610 static if (X.hasValue) 1611 { 1612 import nxt.dynamic_array : Array = DynamicArray; 1613 Array!(X.ElementType) a1; 1614 1615 foreach (const ref key; x1.byKey) 1616 { 1617 auto keyPtr = key in x1; 1618 assert(keyPtr); 1619 a1 ~= X.ElementType(key, (*keyPtr)); 1620 } 1621 1622 assert(x1.length == a1.length); 1623 1624 foreach (aElement; a1[]) 1625 { 1626 auto keyPtr = aElement.key in x1; 1627 assert(keyPtr); 1628 assert((*keyPtr) is aElement.value); 1629 } 1630 } 1631 1632 assert(x1.length == n); 1633 1634 // duplicate x1 1635 1636 auto x2 = x1.dup; 1637 1638 // non-symmetric algorithm so both are needed 1639 assert(x2 == x1); 1640 assert(x1 == x2); 1641 1642 static if (X.hasValue) 1643 { 1644 assert(equal(x1.byKey, x2.byKey)); 1645 assert(equal(x1.byValue, x2.byValue)); 1646 assert(equal(x1.byKeyValue, x2.byKeyValue)); 1647 assert(equal(x1[], x2[])); 1648 } 1649 1650 assert(x1.binCounts.largeCount == 1651 x2.binCounts.largeCount); 1652 1653 static assert(!__traits(compiles, { const _ = x1 < x2; })); // no ordering 1654 1655 assert(x2.length == n); 1656 1657 // empty x1 1658 1659 foreach (immutable key; 0 .. n) 1660 { 1661 static if (X.hasValue) 1662 { 1663 const element = X.ElementType(key, V.init); 1664 } 1665 else 1666 { 1667 const element = key; 1668 } 1669 1670 assert(x1.length == n - key); 1671 1672 auto elementFound = key in x1; 1673 assert(elementFound); 1674 static if (X.hasValue) 1675 { 1676 assert(*elementFound is element.value); 1677 } 1678 1679 assert(x1.remove(key)); 1680 assert(x1.length == n - key - 1); 1681 1682 static if (!X.hasValue) 1683 { 1684 assert(!x1.contains(key)); 1685 } 1686 assert(key !in x1); 1687 assert(!x1.remove(key)); 1688 assert(x1.length == n - key - 1); 1689 } 1690 1691 assert(x1.binCounts.largeCount == 0); 1692 1693 assert(x1.length == 0); 1694 1695 x1.clear(); 1696 assert(x1.length == 0); 1697 1698 // empty x2 1699 1700 assert(x2.length == n); // should be not affected by emptying of x1 1701 1702 foreach (immutable key; 0 .. n) 1703 { 1704 static if (X.hasValue) 1705 { 1706 const element = X.ElementType(key, V.init); 1707 } 1708 else 1709 { 1710 const element = key; 1711 } 1712 1713 assert(x2.length == n - key); 1714 1715 assert(key in x2); 1716 1717 assert(x2.remove(key)); 1718 assert(x2.length == n - key - 1); 1719 1720 assert(key !in x2); 1721 assert(!x2.remove(key)); 1722 assert(x2.length == n - key - 1); 1723 } 1724 1725 assert(x2.binCounts.largeCount == 0); 1726 1727 assert(x2.length == 0); 1728 1729 x2.clear(); 1730 assert(x2.length == 0); 1731 } 1732 } 1733 1734 /// range checking 1735 @trusted pure unittest 1736 { 1737 import std.exception : assertThrown, assertNotThrown; 1738 import core.exception : RangeError; 1739 import nxt.uncopyable_sample : V = SomeUncopyable; 1740 1741 immutable n = 11; 1742 1743 alias K = uint; 1744 1745 alias X = SSOHashMapOrSet!(K, V, null, FNV!(64, true)); 1746 1747 auto s = X.withCapacity(n); 1748 1749 void dummy(ref V value) {} 1750 1751 assertThrown!RangeError(dummy(s[K.init])); 1752 1753 foreach (immutable uint i; 0 .. n) 1754 { 1755 s[i] = V(i); 1756 assertNotThrown!RangeError(dummy(s[i])); 1757 } 1758 1759 foreach (immutable uint i; 0 .. n) 1760 { 1761 s.remove(i); 1762 assertThrown!RangeError(dummy(s[i])); 1763 } 1764 1765 s[K.init] = V.init; 1766 auto vp = K.init in s; 1767 static assert(is(typeof(vp) == V*)); 1768 assert((*vp) == V.init); 1769 1770 s.remove(K.init); 1771 assert(K.init !in s); 1772 1773 X t; 1774 t.reserveExtra(4096); 1775 assert(t.binCount == 8192); 1776 1777 t.clear(); 1778 } 1779 1780 /// class as value 1781 @trusted pure unittest 1782 { 1783 import std.exception : assertThrown, assertNotThrown; 1784 import core.exception : RangeError; 1785 1786 immutable n = 11; 1787 1788 alias K = uint; 1789 class V 1790 { 1791 this(uint data) { this.data = data; } 1792 uint data; 1793 } 1794 1795 alias X = SSOHashMapOrSet!(K, V, null, FNV!(64, true)); 1796 1797 auto s = X.withCapacity(n); 1798 1799 void dummy(ref V value) {} 1800 1801 assertThrown!RangeError(dummy(s[K.init])); 1802 1803 foreach (immutable uint i; 0 .. n) 1804 { 1805 s[i] = new V(i); 1806 assertNotThrown!RangeError(dummy(s[i])); 1807 } 1808 1809 // test range 1810 auto sr = s.byKeyValue; 1811 assert(sr.length == n); 1812 foreach (immutable uint i; 0 .. n) 1813 { 1814 sr.popFront(); 1815 assert(sr.length == n - i - 1); 1816 } 1817 1818 foreach (immutable uint i; 0 .. n) 1819 { 1820 s.remove(i); 1821 assertThrown!RangeError(dummy(s[i])); 1822 } 1823 1824 s[K.init] = V.init; 1825 auto vp = K.init in s; 1826 static assert(is(typeof(vp) == V*)); 1827 1828 s.remove(K.init); 1829 assert(K.init !in s); 1830 1831 X t; 1832 t.reserveExtra(4096); 1833 assert(t.binCount == 8192); 1834 } 1835 1836 /// constness inference of ranges 1837 pure nothrow unittest 1838 { 1839 alias K = uint; 1840 class V 1841 { 1842 this(uint data) { this.data = data; } 1843 uint data; 1844 } 1845 1846 alias X = SSOHashMapOrSet!(K, V, null, FNV!(64, true)); 1847 const x = X(); 1848 1849 foreach (e; x.byKey) 1850 { 1851 static assert(is(typeof(e) == const(X.KeyType))); 1852 } 1853 1854 foreach (e; x.byValue) 1855 { 1856 static assert(is(typeof(e) == X.ValueType)); // TODO should be const(X.ValueType) 1857 } 1858 1859 foreach (e; x.byKeyValue) 1860 { 1861 static assert(is(typeof(e.key) == const(X.KeyType))); 1862 static assert(is(typeof(e.value) == const(X.ValueType))); 1863 static assert(is(typeof(e) == const(X.ElementType))); 1864 } 1865 } 1866 1867 /// range key constness and value mutability with `class` value 1868 pure nothrow unittest 1869 { 1870 struct K 1871 { 1872 @disable this(this); 1873 uint value; 1874 } 1875 1876 class V 1877 { 1878 this(uint data) { this.data = data; } 1879 uint data; 1880 } 1881 1882 alias X = SSOHashMapOrSet!(K, V, null, FNV!(64, true)); 1883 auto x = X(); 1884 1885 x[K(42)] = new V(43); 1886 1887 assert(x.length == 1); 1888 1889 foreach (e; x.byValue) // `e` is auto ref 1890 { 1891 static assert(is(typeof(e) == X.ValueType)); // mutable access to value 1892 assert(e.data == 43); 1893 1894 // value mutation side effects 1895 e.data += 1; 1896 assert(e.data == 44); 1897 e.data -= 1; 1898 assert(e.data == 43); 1899 } 1900 1901 foreach (ref e; x.byKeyValue) // `e` is auto ref 1902 { 1903 static assert(is(typeof(e.key) == const(X.KeyType))); // const access to key 1904 1905 static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value 1906 1907 assert(e.value.data == 43); 1908 1909 // value mutation side effects 1910 e.value.data += 1; 1911 assert(e.value.data == 44); 1912 e.value.data -= 1; 1913 assert(e.value.data == 43); 1914 } 1915 } 1916 1917 version(unittest) 1918 { 1919 import std.algorithm.comparison : equal; 1920 import nxt.digestx.fnv : FNV; 1921 import nxt.array_help : s; 1922 }