1 /// 2 module stdx.collections.hashtable; 3 4 import stdx.collections.common; 5 6 debug(CollectionHashtable) import std.stdio; 7 8 version(unittest) 9 { 10 import std.experimental.allocator.mallocator; 11 import std.experimental.allocator.building_blocks.stats_collector; 12 import std.experimental.allocator : allocatorObject, 13 RCIAllocator, RCISharedAllocator; 14 15 private alias SCAlloc = StatsCollector!(Mallocator, Options.bytesUsed); 16 } 17 18 /// 19 struct Hashtable(K, V) 20 { 21 import std.experimental.allocator : RCIAllocator, RCISharedAllocator; 22 import std.traits : isImplicitlyConvertible; 23 import std.typecons : Tuple, Nullable; 24 import std.algorithm.mutation : move; 25 import stdx.collections.slist : SList; 26 import stdx.collections.array : Array; 27 28 private: 29 alias KVPair = Tuple!(K, "key", V, "value"); 30 Array!(SList!(KVPair)) _buckets; 31 Array!size_t _numElems; // This needs to be ref counted 32 static enum double loadFactor = 0.75; 33 AllocatorHandler _allocator; 34 35 // Allocators internals 36 37 /// Constructs the ouroboros allocator from allocator if the ouroboros 38 // allocator wasn't previously set 39 /*@nogc*/ nothrow pure @safe 40 bool setAllocator(A)(ref A allocator) 41 if (is(A == RCIAllocator) || is(A == RCISharedAllocator)) 42 { 43 if (_allocator.isNull) 44 { 45 auto a = typeof(_allocator)(allocator); 46 move(a, _allocator); 47 48 _buckets = Array!(SList!(KVPair))(allocator, SList!(KVPair)(allocator)); 49 //_buckets = Array!(SList!(KVPair))(allocator); 50 _numElems = Array!size_t(allocator, 0UL); 51 52 return true; 53 } 54 return false; 55 } 56 57 public: 58 /** 59 * Constructs a qualified hashtable that will use the provided 60 * allocator object. For `immutable` objects, a `RCISharedAllocator` must 61 * be supplied. 62 * 63 * Params: 64 * allocator = a $(REF RCIAllocator, std,experimental,allocator) or 65 * $(REF RCISharedAllocator, std,experimental,allocator) 66 * allocator object 67 * 68 * Complexity: $(BIGOH 1) 69 */ 70 this(A, this Q)(A allocator) 71 if (!is(Q == shared) 72 && (is(A == RCISharedAllocator) || !is(Q == immutable)) 73 && (is(A == RCIAllocator) || is(A == RCISharedAllocator))) 74 { 75 debug(CollectionHashtable) 76 { 77 writefln("Array.ctor: begin"); 78 scope(exit) writefln("Array.ctor: end"); 79 } 80 static if (is(Q == immutable) || is(Q == const)) 81 { 82 V[K] empty; 83 this(allocator, empty); 84 } 85 else 86 { 87 setAllocator(allocator); 88 } 89 } 90 91 /// 92 static if (is(K == int)) 93 @safe unittest 94 { 95 import std.experimental.allocator : theAllocator, processAllocator; 96 97 auto h = Hashtable!(int, int)(theAllocator); 98 auto ch = const Hashtable!(int, int)(processAllocator); 99 auto ih = immutable Hashtable!(int, int)(processAllocator); 100 } 101 102 /** 103 * Constructs a qualified hashtable out of an associative array. 104 * Because no allocator was provided, the hashtable will use the 105 * $(REF, GCAllocator, std,experimental,allocator,gc_allocator). 106 * 107 * Params: 108 * assocArr = an associative array 109 * 110 * Complexity: $(BIGOH m), where `m` is the number of (key, value) pairs 111 * in the associative array. 112 */ 113 this(U, this Q)(U assocArr) 114 if (is(U == Value[Key], Value : V, Key : K)) 115 { 116 this(defaultAllocator!(typeof(this)), assocArr); 117 } 118 119 /// 120 static if (is(K == int)) 121 @safe unittest 122 { 123 import std.algorithm.comparison : equal; 124 125 auto h = Hashtable!(int, int)([1 : 10]); 126 assert(equal(h.keys(), [1])); 127 assert(equal(h.values(), [10])); 128 } 129 130 /** 131 * Constructs a qualified hashtable out of an associative array 132 * that will use the provided allocator object. 133 * For `immutable` objects, a `RCISharedAllocator` must be supplied. 134 * 135 * Params: 136 * allocator = a $(REF RCIAllocator, std,experimental,allocator) or 137 * $(REF RCISharedAllocator, std,experimental,allocator) 138 * allocator object 139 * assocArr = an associative array 140 * 141 * Complexity: $(BIGOH m), where `m` is the number of (key, value) pairs 142 * in the associative array. 143 */ 144 this(A, U, this Q)(A allocator, U assocArr) 145 if (!is(Q == shared) 146 && (is(A == RCISharedAllocator) || !is(Q == immutable)) 147 && (is(A == RCIAllocator) || is(A == RCISharedAllocator)) 148 && is(U == Value[Key], Value : V, Key : K)) 149 { 150 debug(CollectionHashtable) 151 { 152 writefln("Hashtable.ctor: begin"); 153 scope(exit) writefln("Hashtable.ctor: end"); 154 } 155 static if (is(Q == immutable) || is(Q == const)) 156 { 157 // Build a mutable hashtable on the stack and pass ownership to this 158 159 // TODO: is this ok? 160 auto tmp = Hashtable!(K, V)(assocArr); 161 //_buckets = cast(typeof(_buckets))(tmp._buckets); 162 _buckets = typeof(_buckets)(allocator, tmp._buckets); 163 //_numElems = cast(typeof(_numElems))(tmp._numElems); 164 _numElems = typeof(_numElems)(allocator, tmp._numElems); 165 166 _allocator = immutable AllocatorHandler(allocator); 167 } 168 else 169 { 170 setAllocator(allocator); 171 insert(assocArr); 172 } 173 } 174 175 /// 176 static if (is(K == int)) 177 @safe unittest 178 { 179 import std.algorithm.comparison : equal; 180 import stdx.collections.array : Array; 181 182 { 183 auto h = Hashtable!(int, int)([1 : 10]); 184 assert(equal(h.keys(), [1])); 185 assert(equal(h.values(), [10])); 186 } 187 188 { 189 auto h = immutable Hashtable!(int, int)([1 : 10]); 190 assert(equal(h.values(), Array!int([10]))); 191 } 192 } 193 194 static if (is(K == int)) 195 /*nothrow*/ pure @safe unittest 196 { 197 auto h = Hashtable!(int, int)([1 : 42]); 198 199 // Infer safety 200 static assert(!__traits(compiles, () @safe { Hashtable!(Unsafe, int)([Unsafe(1) : 1]); })); 201 static assert(!__traits(compiles, () @safe { auto s = const Hashtable!(Unsafe, int)([Unsafe(1) : 1]); })); 202 static assert(!__traits(compiles, () @safe { auto s = immutable Hashtable!(Unsafe, int)([Unsafe(1) : 1]); })); 203 204 static assert(!__traits(compiles, () @safe { Hashtable!(UnsafeDtor, int)([UnsafeDtor(1) : 1]); })); 205 static assert(!__traits(compiles, () @safe { auto s = const Hashtable!(UnsafeDtor, int)([UnsafeDtor(1) : 1]); })); 206 static assert(!__traits(compiles, () @safe { auto s = immutable Hashtable!(UnsafeDtor, int)([UnsafeDtor(1) : 1]); })); 207 208 // Infer purity 209 static assert(!__traits(compiles, () @safe { Hashtable!(Impure, int)([Impure(1) : 1]); })); 210 static assert(!__traits(compiles, () @safe { auto s = const Hashtable!(Impure, int)([Impure(1) : 1]); })); 211 static assert(!__traits(compiles, () @safe { auto s = immutable Hashtable!(Impure, int)([Impure(1) : 1]); })); 212 213 static assert(!__traits(compiles, () @safe { Hashtable!(ImpureDtor, int)([ImpureDtor(1) : 1]); })); 214 static assert(!__traits(compiles, () @safe { auto s = const Hashtable!(ImpureDtor, int)([ImpureDtor(1) : 1]); })); 215 static assert(!__traits(compiles, () @safe { auto s = immutable Hashtable!(ImpureDtor, int)([ImpureDtor(1) : 1]); })); 216 } 217 218 /** 219 * Insert the (key, value) pairs of an associative array into the 220 * hashtable. 221 * 222 * If no allocator was provided when the list was created, the 223 * $(REF, GCAllocator, std,experimental,allocator,gc_allocator) will be used. 224 * 225 * Params: 226 * assocArr = an associative array 227 * 228 * Complexity: $(BIGOH m), where `m` is the number of (key, value) pairs 229 * in the associative array. 230 */ 231 size_t insert(U)(U assocArr) 232 if (is(U == Value[Key], Value : V, Key : K)) 233 { 234 debug(CollectionHashtable) 235 { 236 writefln("Hashtable.insert: begin"); 237 scope(exit) writefln("Hashtable.insert: end"); 238 } 239 240 // Will be optimized away, but the type system infers T's safety 241 if (0) { K t = K.init; V v = V.init; } 242 243 // Ensure we have an allocator. If it was already set, this will do nothing 244 auto a = threadAllocatorObject(); 245 setAllocator(a); 246 247 if (_buckets.empty) 248 { 249 auto reqCap = requiredCapacity(assocArr.length); 250 _buckets.reserve(reqCap); 251 _buckets.forceLength(reqCap); 252 _numElems.reserve(1); 253 _numElems.forceLength(1); 254 } 255 foreach(k, v; assocArr) 256 { 257 size_t pos = k.hashOf & (_buckets.length - 1); 258 if (_buckets[pos].empty) 259 { 260 _buckets[pos] = SList!(KVPair)(_allocator.getLocalAlloc, KVPair(k, v)); 261 } 262 else 263 { 264 _buckets[pos].insert(0, KVPair(k, v)); 265 } 266 } 267 _numElems[0] += assocArr.length; 268 return assocArr.length; 269 } 270 271 /// 272 static if (is(K == int)) 273 @safe unittest 274 { 275 import std.algorithm.comparison : equal; 276 277 auto h = Hashtable!(int, int)(); 278 assert(h.length == 0); 279 h.insert([1 : 10]); 280 assert(equal(h.keys(), [1])); 281 assert(equal(h.values(), [10])); 282 } 283 284 private @nogc nothrow pure @safe 285 size_t requiredCapacity(size_t numElems) 286 { 287 static enum size_t maxPow2 = cast(size_t)(1) << (size_t.sizeof * 8 - 1); 288 while (numElems & (numElems - 1)) 289 { 290 numElems &= (numElems - 1); 291 } 292 return numElems < maxPow2 ? 2 * numElems : maxPow2; 293 } 294 295 /** 296 * Returns number of key-value pairs. 297 * 298 * Returns: 299 * a positive integer. 300 * 301 * Complexity: $(BIGOH 1). 302 */ 303 @nogc nothrow pure @safe 304 size_t length() const 305 { 306 return _numElems.empty ? 0 : _numElems[0]; 307 } 308 309 /// 310 static if (is(K == int)) 311 @safe unittest 312 { 313 import std.algorithm.comparison : equal; 314 315 auto h = Hashtable!(int, int)(); 316 assert(h.length == 0); 317 h.insert([1 : 10]); 318 assert(h.length == 1); 319 } 320 321 /** 322 * Returns number of buckets. 323 * 324 * Returns: 325 * a positive integer. 326 * 327 * Complexity: $(BIGOH 1). 328 */ 329 @nogc nothrow pure @safe 330 size_t size() const 331 { 332 return _buckets.length; 333 } 334 335 /** 336 * Check if the hashtable is empty. 337 * 338 * Returns: 339 * `true` if there are no elements in the hashtable; `false` otherwise. 340 * 341 * Complexity: $(BIGOH 1). 342 */ 343 @nogc nothrow pure @safe 344 bool empty() const 345 { 346 return _buckets.empty || _numElems.empty || _numElems[0] == 0; 347 } 348 349 /** 350 * Provide access to the first value in the hashtable. The user must check 351 * that the hashtable isn't `empty`, prior to calling this function. 352 * There is no guarantee of the order of the values, as they are placed in 353 * the hashtable based on the result of the hash function. 354 * 355 * Returns: 356 * a reference to the first found value. 357 * 358 * Complexity: $(BIGOH length). 359 */ 360 ref auto front(this _)() 361 { 362 debug(CollectionHashtable) 363 { 364 writefln("Hashtable.front: begin"); 365 scope(exit) writefln("Hashtable.front: end"); 366 } 367 auto tmpBuckets = _buckets; 368 while ((!tmpBuckets.empty) && tmpBuckets.front.empty) 369 { 370 tmpBuckets.popFront; 371 } 372 assert(!tmpBuckets.empty, "Hashtable.front: Hashtable is empty"); 373 return tmpBuckets.front.front; 374 } 375 376 void popFront() 377 { 378 debug(CollectionHashtable) 379 { 380 writefln("Hashtable.popFront: begin"); 381 scope(exit) writefln("Hashtable.popFront: end"); 382 } 383 if (!_buckets.isUnique) 384 { 385 _buckets = _buckets.dup(); 386 _numElems = _numElems.dup(); 387 } 388 while ((!_buckets.empty) && _buckets.front.empty) 389 { 390 _buckets.popFront; 391 } 392 assert(!_buckets.empty, "Hashtable.front: Hashtable is empty"); 393 _buckets.front.popFront; 394 --_numElems[0]; 395 // Find the next non-empty bucketList 396 if (_buckets.front.empty) 397 { 398 while ((!_buckets.empty) && _buckets.front.empty) 399 { 400 _buckets.popFront; 401 } 402 } 403 } 404 405 private const 406 void getKeyValues(string kv, BuckQual, ListQual, ArrQual)(BuckQual b, ListQual l, ref ArrQual values) 407 { 408 if (b.empty) 409 { 410 return; 411 } 412 else if (l.empty) 413 { 414 auto bTail = b.tail; 415 if (!bTail.empty) 416 { 417 getKeyValues!kv(bTail, bTail.front, values); 418 } 419 } 420 else 421 { 422 static if (kv.length) 423 { 424 mixin("values ~= l.front." ~ kv ~ ";"); 425 } 426 else 427 { 428 mixin("values ~= l.front;"); 429 } 430 getKeyValues!kv(b, l.tail, values); 431 } 432 } 433 434 /** 435 * Get an array with the existing keys in the hashtable. 436 * 437 * Returns: 438 * an `Array!K` array of keys 439 * 440 * Complexity: $(BIGOH size). 441 */ 442 Array!K keys(this _)() 443 { 444 debug(CollectionHashtable) 445 { 446 writefln("Hashtable.keys: begin"); 447 scope(exit) writefln("Hashtable.keys: end"); 448 } 449 450 Array!K keys; 451 auto tmp = _buckets; 452 if (!_buckets.empty) 453 //if (!tmp.empty) 454 { 455 //getKeyValues!("key")(tmp, tmp.front, keys); 456 getKeyValues!("key")(_buckets, _buckets.front, keys); 457 } 458 return keys; 459 } 460 461 /** 462 * Get an array with the existing values in the hashtable. 463 * 464 * Returns: 465 * an `Array!V` array of values 466 * 467 * Complexity: $(BIGOH length). 468 */ 469 Array!V values(this _)() 470 { 471 debug(CollectionHashtable) 472 { 473 writefln("Hashtable.values: begin"); 474 scope(exit) writefln("Hashtable.values: end"); 475 } 476 477 Array!V values; 478 if (!_buckets.empty) 479 { 480 getKeyValues!("value")(_buckets, _buckets.front, values); 481 } 482 483 return values; 484 } 485 486 /** 487 * Get an array with the existing key-value pairs in the hashtable. 488 * 489 * Returns: 490 * an `Array!(Tuple!(K, "key", V, "value"))` array of key-value pairs 491 * 492 * Complexity: $(BIGOH length). 493 */ 494 Array!(Tuple!(K, "key", V, "value")) keyValuePairs(this _)() 495 { 496 debug(CollectionHashtable) 497 { 498 writefln("Hashtable.keyValuePairs: begin"); 499 scope(exit) writefln("Hashtable.keyValuePairs: end"); 500 } 501 502 Array!KVPair pairs; 503 if (!_buckets.empty) 504 { 505 getKeyValues!("")(_buckets, _buckets.front, pairs); 506 } 507 508 return pairs; 509 } 510 511 private const 512 Nullable!V getValue(ListQual)(ListQual list, ref K key) 513 { 514 if (list.empty) 515 { 516 return Nullable!V.init; 517 } 518 else if (list.front.key == key) 519 { 520 return Nullable!V(list.front.value); 521 } 522 else 523 { 524 return getValue(list.tail, key); 525 } 526 } 527 528 /** 529 * Get the value corresponding to the given `key`; if it doesn't exist 530 * return `nullValue`. 531 * 532 * Params: 533 * key = the key corresponding to a value 534 * nullValue = a value that will be returned if there is no such 535 * key-value pair 536 * 537 * Returns: 538 * the corresponding key value, or `nullValue`. 539 * 540 * Complexity: $(BIGOH n), where n is the number of elements in the 541 * corresponding key bucket. 542 */ 543 V get(this _)(K key, V nullValue) 544 { 545 debug(CollectionHashtable) 546 { 547 writefln("Hashtable.get: begin"); 548 scope(exit) writefln("Hashtable.get: end"); 549 } 550 551 size_t pos = key.hashOf & (_buckets.length - 1); 552 auto result = getValue(_buckets[pos], key); 553 if (!result.isNull) 554 { 555 return result.get; 556 } 557 return nullValue; 558 } 559 560 /** 561 * Get a `Nullable!V` corresponding to the given `key` index. 562 * 563 * Params: 564 * key = the key corresponding to a value 565 * 566 * Returns: 567 * a nullable value 568 * 569 * Complexity: $(BIGOH n), where n is the number of elements in the 570 * corresponding key bucket. 571 */ 572 Nullable!V opIndex(this _)(K key) 573 { 574 debug(CollectionHashtable) 575 { 576 writefln("Hashtable.opIndex: begin"); 577 scope(exit) writefln("Hashtable.opIndex: end"); 578 } 579 580 size_t pos = key.hashOf & (_buckets.length - 1); 581 return getValue(_buckets[pos], key); 582 } 583 584 /** 585 * Apply a unary operation to the value corresponding to `key`. 586 * If there isn't such a value, return a `V.init` wrapped inside a 587 * `Nullable`. 588 * 589 * Params: 590 * key = the key corresponding to a value 591 * 592 * Returns: 593 * a nullable value corresponding to the result or `V.init`. 594 * 595 * Complexity: $(BIGOH n), where n is the number of elements in the 596 * corresponding key bucket. 597 */ 598 Nullable!V opIndexUnary(string op)(K key) 599 { 600 debug(CollectionHashtable) 601 { 602 writefln("Hashtable.opIndexUnary!" ~ op ~ ": begin"); 603 scope(exit) writefln("Hashtable.opIndexUnary!" ~ op ~ ": end"); 604 } 605 606 size_t pos = key.hashOf & (_buckets.length - 1); 607 foreach(ref pair; _buckets[pos]) 608 { 609 if (pair.key == key) 610 { 611 return Nullable!V(mixin(op ~ "pair.value")); 612 } 613 } 614 return Nullable!V.init; 615 } 616 617 /** 618 * Assign `val` to the element corresponding to `key`. 619 * If there isn't such a value, return a `V.init` wrapped inside a 620 * `Nullable`. 621 * 622 * Params: 623 * val = the value to be set 624 * key = the key corresponding to a value 625 * 626 * Returns: 627 * a nullable value corresponding to the result or `V.init`. 628 * 629 * Complexity: $(BIGOH n), where n is the number of elements in the 630 * corresponding key bucket. 631 */ 632 Nullable!V opIndexAssign(U)(U val, K key) 633 if (isImplicitlyConvertible!(U, V)) 634 { 635 debug(CollectionHashtable) 636 { 637 writefln("Hashtable.opIndexAssign: begin"); 638 scope(exit) writefln("Hashtable.opIndexAssign: end"); 639 } 640 641 size_t pos = key.hashOf & (_buckets.length - 1); 642 foreach(ref pair; _buckets[pos]) 643 { 644 if (pair.key == key) 645 { 646 return Nullable!V(pair.value); 647 } 648 } 649 return Nullable!V.init; 650 } 651 652 /** 653 * Assign to the element corresponding to `key` the result of 654 * applying `op` to the current value. 655 * If there isn't such a value, return a `V.init` wrapped inside a 656 * `Nullable`. 657 * 658 * Params: 659 * val = the value to be used with `op` 660 * key = the key corresponding to a value 661 * 662 * Returns: 663 * a nullable value corresponding to the result or `V.init`. 664 * 665 * Complexity: $(BIGOH n), where n is the number of elements in the 666 * corresponding key bucket. 667 */ 668 Nullable!V opIndexOpAssign(string op, U)(U val, K key) 669 if (isImplicitlyConvertible!(U, V)) 670 { 671 debug(CollectionHashtable) 672 { 673 writefln("Hashtable.opIndexOpAssign: begin"); 674 scope(exit) writefln("Hashtable.opIndexOpAssign: end"); 675 } 676 677 size_t pos = key.hashOf & (_buckets.length - 1); 678 foreach(ref pair; _buckets[pos]) 679 { 680 if (pair.key == key) 681 { 682 return Nullable!V(mixin("pair.value" ~ op ~ "= val")); 683 } 684 } 685 return Nullable!V.init; 686 } 687 688 auto ref opBinary(string op, U)(auto ref U rhs) 689 if (op == "~" && 690 (is (U == typeof(this)) 691 || is (U : T) 692 || (isInputRange!U && isImplicitlyConvertible!(ElementType!U, T)) 693 )); 694 695 /** 696 * Assign `rhs` to this hashtable. The current hashtable will now become 697 * another reference to `rhs`, unless `rhs` is `null`, in which case the 698 * current hashtable will become empty. If `rhs` refers to the current 699 * hashtable nothing will happen. 700 * 701 * If there are no more references to the previous hashtable the previous 702 * hashtable will be destroyed; this leads to a $(BIGOH length) complexity. 703 * 704 * Params: 705 * rhs = a reference to a hashtable 706 * 707 * Returns: 708 * a reference to this hashtable 709 * 710 * Complexity: $(BIGOH length). 711 */ 712 auto ref opAssign()(auto ref typeof(this) rhs) 713 { 714 debug(CollectionHashtable) 715 { 716 writefln("Hashtable.opAssign: begin"); 717 scope(exit) writefln("Hashtable.opAssign: end"); 718 } 719 720 _buckets = rhs._buckets; 721 _numElems = rhs._numElems; 722 _ouroborosAllocator = rhs._ouroborosAllocator; 723 return this; 724 } 725 726 void remove(K key); 727 728 /** 729 * Removes all the elements in the current hashtable. 730 * 731 * Complexity: $(BIGOH length). 732 */ 733 void clear() 734 { 735 debug(CollectionHashtable) 736 { 737 writefln("Hashtable.clear: begin"); 738 scope(exit) writefln("Hashtable.clear: end"); 739 } 740 auto alloc = _allocator.getLocalAlloc; 741 _buckets = Array!(SList!(KVPair))(alloc, SList!(KVPair)(alloc)); 742 _numElems = Array!size_t(alloc, 0UL); 743 } 744 745 void rehash(); 746 } 747 748 version(unittest) private /*nothrow*/ pure @safe 749 void testSimple(RCIAllocator allocator) 750 { 751 import std.algorithm.comparison : equal; 752 753 auto h = Hashtable!(int, int)(allocator, [1 : 10]); 754 755 assert(equal(h.keys(), [1])); 756 assert(equal(h.values(), [10])); 757 assert(h.get(1, -1) == 10); 758 assert(h.get(200, -1) == -1); 759 assert(--h[1] == 9); 760 assert(h.get(1, -1) == 9); 761 } 762 763 @safe unittest 764 { 765 import std.conv; 766 SCAlloc statsCollectorAlloc; 767 { 768 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 769 () /*nothrow pure*/ @safe { 770 testSimple(_allocator); 771 }(); 772 } 773 auto bytesUsed = statsCollectorAlloc.bytesUsed; 774 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 775 ~ to!string(bytesUsed) ~ " bytes"); 776 } 777 778 version(unittest) private /*nothrow*/ pure @safe 779 void testIndex(RCIAllocator allocator) 780 { 781 import std.algorithm.comparison : equal; 782 import std.typecons : Tuple; 783 784 auto h = Hashtable!(int, int)(); 785 assert(h.length == 0); 786 h.insert([1 : 10]); 787 assert(h.length == 1); 788 assert(h._buckets[0].front == Tuple!(int, int)(1, 10)); 789 h.clear(); 790 assert(h.length == 0); 791 assert(h.empty); 792 793 h.insert([1 : 10]); 794 assert(equal(h.keys(), [1])); 795 } 796 797 @safe unittest 798 { 799 import std.conv; 800 SCAlloc statsCollectorAlloc; 801 { 802 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 803 () /*nothrow pure*/ @safe { 804 testIndex(_allocator); 805 }(); 806 } 807 auto bytesUsed = statsCollectorAlloc.bytesUsed; 808 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 809 ~ to!string(bytesUsed) ~ " bytes"); 810 } 811 812 version(unittest) private /*nothrow*/ pure @safe 813 void testSimpleImmutable(RCIAllocator allocator) 814 { 815 import std.algorithm.comparison : equal; 816 import std.typecons : Tuple; 817 import stdx.collections.array : Array; 818 819 auto h = immutable Hashtable!(int, int)([1 : 10]); 820 assert(h._buckets[0].front == Tuple!(int, int)(1, 10)); 821 assert(h.get(1, -1) == 10); 822 assert(equal(h.values(), Array!int([10]))); 823 assert(equal(h.keyValuePairs(), Array!(Tuple!(int, int))(Tuple!(int, int)(1, 10)))); 824 } 825 826 @safe unittest 827 { 828 import std.conv; 829 SCAlloc statsCollectorAlloc; 830 { 831 auto _allocator = (() @trusted => allocatorObject(&statsCollectorAlloc))(); 832 () /*nothrow pure*/ @safe { 833 testSimpleImmutable(_allocator); 834 }(); 835 } 836 auto bytesUsed = statsCollectorAlloc.bytesUsed; 837 assert(bytesUsed == 0, "SList ref count leaks memory; leaked " 838 ~ to!string(bytesUsed) ~ " bytes"); 839 }