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