1 /** 2 Some additional containers. 3 4 Copyright: © 2019 Arne Ludwig <arne.ludwig@posteo.de> 5 License: Subject to the terms of the MIT license, as written in the 6 included LICENSE file. 7 Authors: Arne Ludwig <arne.ludwig@posteo.de> 8 */ 9 module dalicious.container; 10 11 import core.exception; 12 import std.math; 13 import std.traits; 14 import std.typecons; 15 16 17 /// An array-based implementation of a ring buffer. 18 struct RingBuffer(T, size_t staticBufferSize = size_t.max) 19 { 20 private: 21 22 static if (staticBufferSize < size_t.max) 23 T[staticBufferSize] _buffer; 24 else 25 T[] _buffer; 26 ptrdiff_t _frontPtr = -1; 27 ptrdiff_t _backPtr; 28 29 public: 30 31 static if (staticBufferSize == size_t.max) 32 { 33 this(size_t bufferSize) pure nothrow @safe 34 { 35 this._buffer = new T[bufferSize]; 36 } 37 38 39 void reserve(size_t num) pure nothrow @safe 40 { 41 if (num <= bufferSize) 42 return; 43 44 if (empty) 45 { 46 _buffer.length = num; 47 } 48 else 49 { 50 auto newBuffer = new T[num]; 51 52 size_t i; 53 foreach_reverse (e; this) 54 newBuffer[i++] = e; 55 56 this._frontPtr = cast(ptrdiff_t) length - 1; 57 this._backPtr = 0; 58 this._buffer = newBuffer; 59 } 60 } 61 } 62 63 64 static if (staticBufferSize < size_t.max) 65 enum bufferSize = _buffer.length; 66 else 67 @property size_t bufferSize() const pure nothrow @safe @nogc 68 { 69 return _buffer.length; 70 } 71 72 alias capacity = bufferSize; 73 74 75 @property RingBuffer!(T, staticBufferSize) save() const pure nothrow @trusted @nogc 76 { 77 return cast(typeof(return)) this; 78 } 79 80 81 @property bool empty() const pure nothrow @safe @nogc 82 { 83 return _frontPtr < _backPtr || bufferSize == 0; 84 } 85 86 87 @property size_t length() const pure nothrow @safe @nogc 88 { 89 return empty ? 0 : _frontPtr - _backPtr + 1; 90 } 91 92 93 void clear() pure nothrow @safe @nogc 94 { 95 this._frontPtr = -1; 96 this._backPtr = 0; 97 } 98 99 100 @property auto ref T front() pure nothrow @safe @nogc 101 { 102 assert(!empty, "Attempting to fetch the front of an empty RingBuffer"); 103 104 return _buffer[indexOf(_frontPtr)]; 105 } 106 107 108 @property void front(T newFront) 109 { 110 assert(!empty, "Attempting to assign to the front of an empty RingBuffer"); 111 112 this.front() = newFront; 113 } 114 115 116 @property auto ref T back() pure nothrow @safe @nogc 117 { 118 assert(!empty, "Attempting to fetch the back of an empty RingBuffer"); 119 120 return _buffer[indexOf(_backPtr)]; 121 } 122 123 124 @property void back(T newBack) 125 { 126 assert(!empty, "Attempting to assign to the back of an empty RingBuffer"); 127 128 this.back() = newBack; 129 } 130 131 132 void popFront() pure nothrow @safe @nogc 133 { 134 assert(!empty, "Attempting to popFront an empty RingBuffer"); 135 136 --_frontPtr; 137 normalizePtrs(); 138 } 139 140 141 void popBack() pure nothrow @safe @nogc 142 { 143 assert(!empty, "Attempting to popBack an empty RingBuffer"); 144 145 ++_backPtr; 146 normalizePtrs(); 147 } 148 149 150 void pushFront(T value) pure nothrow @safe @nogc 151 { 152 assert(bufferSize > 0, "Attempting to pushFront an zero-sized RingBuffer"); 153 154 auto wasEmpty = empty; 155 156 ++_frontPtr; 157 if (!wasEmpty && indexOf(_backPtr) == indexOf(_frontPtr)) 158 ++_backPtr; 159 normalizePtrs(); 160 161 front = value; 162 } 163 164 void pushBack(T value) pure nothrow @safe @nogc 165 { 166 assert(bufferSize > 0, "Attempting to pushBack an zero-sized RingBuffer"); 167 168 auto wasEmpty = empty; 169 170 --_backPtr; 171 if (!wasEmpty && indexOf(_backPtr) == indexOf(_frontPtr)) 172 --_frontPtr; 173 normalizePtrs(); 174 175 back = value; 176 } 177 178 alias put = pushBack; 179 180 181 private size_t indexOf(ptrdiff_t ptr) const pure nothrow @safe @nogc 182 { 183 assert(0 <= ptr + 2*bufferSize); 184 185 return cast(size_t) ((ptr + 2*bufferSize) % bufferSize); 186 } 187 188 189 private @property bool areNormalizedPtrs() const pure nothrow @safe @nogc 190 { 191 return abs(_frontPtr) <= bufferSize || abs(_backPtr) <= bufferSize; 192 } 193 194 195 private void normalizePtrs() pure nothrow @safe @nogc 196 { 197 if (areNormalizedPtrs) 198 return; 199 200 if (empty) 201 { 202 _frontPtr = -1; 203 _backPtr = 0; 204 } 205 else 206 { 207 assert(_frontPtr >= _backPtr); 208 if (0 <= _backPtr) 209 { 210 _frontPtr = indexOf(_frontPtr); 211 _backPtr = indexOf(_backPtr); 212 } 213 else 214 { 215 _frontPtr = indexOf(_frontPtr + 2*bufferSize); 216 _backPtr = indexOf(_backPtr + 2*bufferSize); 217 } 218 219 if (_frontPtr < _backPtr) 220 _frontPtr += bufferSize; 221 } 222 } 223 } 224 225 unittest 226 { 227 import std.meta; 228 import std.range.primitives; 229 230 alias Element = int; 231 alias DyanmicRB = RingBuffer!Element; 232 alias StaticRB = RingBuffer!(Element, 10); 233 234 static foreach (alias RB; AliasSeq!(DyanmicRB, StaticRB)) 235 { 236 static assert(isInputRange!RB); 237 static assert(isOutputRange!(RB, Element)); 238 static assert(isForwardRange!RB); 239 static assert(isBidirectionalRange!RB); 240 static assert(is(ElementType!RB == Element)); 241 static assert(hasAssignableElements!RB); 242 static assert(hasLvalueElements!RB); 243 static assert(hasLength!RB); 244 static assert(!isInfinite!RB); 245 } 246 } 247 248 /// Ring buffer stores the `bufferSize` most recent elements. 249 unittest 250 { 251 import std.algorithm; 252 253 auto buffer = RingBuffer!int(5); 254 255 buffer.pushFront(1); 256 buffer.pushFront(2); 257 buffer.pushFront(3); 258 buffer.pushFront(4); 259 buffer.pushFront(5); 260 261 assert(buffer.front == 5); 262 assert(equal(buffer, [5, 4, 3, 2, 1])); 263 264 buffer.pushFront(6); 265 266 assert(buffer.front == 6); 267 assert(equal(buffer, [6, 5, 4, 3, 2])); 268 } 269 270 /// Ring buffer may have a static size. 271 unittest 272 { 273 import std.algorithm; 274 275 auto buffer = RingBuffer!(int, 5)(); 276 277 buffer.pushFront(1); 278 buffer.pushFront(2); 279 buffer.pushFront(3); 280 buffer.pushFront(4); 281 buffer.pushFront(5); 282 283 assert(buffer.front == 5); 284 assert(equal(buffer, [5, 4, 3, 2, 1])); 285 286 buffer.pushFront(6); 287 288 assert(buffer.front == 6); 289 assert(equal(buffer, [6, 5, 4, 3, 2])); 290 } 291 292 /// Elements can be removed. 293 unittest 294 { 295 import std.algorithm; 296 297 auto buffer = RingBuffer!int(5); 298 299 buffer.pushFront(1); 300 buffer.pushFront(2); 301 buffer.pushFront(3); 302 buffer.pushFront(4); 303 buffer.pushFront(5); 304 305 assert(buffer.length == 5); 306 assert(equal(buffer, [5, 4, 3, 2, 1])); 307 308 buffer.popFront(); 309 buffer.popBack(); 310 311 assert(buffer.length == 3); 312 assert(equal(buffer, [4, 3, 2])); 313 } 314 315 /// The buffer is double-ended. 316 unittest 317 { 318 import std.algorithm; 319 320 auto buffer = RingBuffer!int(5); 321 322 buffer.pushFront(1); 323 buffer.pushFront(2); 324 buffer.pushFront(3); 325 buffer.pushFront(4); 326 buffer.pushFront(5); 327 buffer.pushFront(6); 328 329 assert(buffer.front == 6); 330 assert(buffer.back == 2); 331 assert(equal(buffer, [6, 5, 4, 3, 2])); 332 333 buffer.pushBack(1); 334 335 assert(buffer.front == 5); 336 assert(buffer.back == 1); 337 assert(equal(buffer, [5, 4, 3, 2, 1])); 338 } 339 340 /// The buffer can be used as an output range. 341 unittest 342 { 343 import std.algorithm; 344 import std.range; 345 346 auto buffer = RingBuffer!int(32); 347 348 iota(8).copy(&buffer); 349 350 assert(buffer.front == 0); 351 buffer.popFront(); 352 353 assert(buffer.front == 1); 354 buffer.popFront(); 355 356 assert(equal(buffer, iota(2, 8))); 357 } 358 359 unittest 360 { 361 import std.algorithm; 362 import std.range; 363 364 auto buffer = RingBuffer!int(32); 365 366 auto filledBuffer = iota(8).copy(buffer); 367 368 assert(!equal(buffer, filledBuffer)); 369 assert(equal(filledBuffer, iota(8))); 370 } 371 372 unittest 373 { 374 import std.algorithm; 375 376 auto buffer = RingBuffer!int(5); 377 378 buffer.pushBack(1); // [1] 379 buffer.pushBack(2); // [1, 2] 380 buffer.pushBack(3); // [1, 2, 3] 381 buffer.pushBack(4); // [1, 2, 3, 4] 382 buffer.pushBack(5); // [1, 2, 3, 4, 5] 383 buffer.pushBack(6); // [2, 3, 4, 5, 6] 384 385 assert(buffer.front == 2); 386 assert(buffer.back == 6); 387 assert(equal(buffer, [2, 3, 4, 5, 6])); 388 } 389 390 unittest 391 { 392 import std.algorithm; 393 394 auto buffer = RingBuffer!int(5); 395 396 buffer.pushFront(1); // [1] 397 buffer.pushFront(2); // [2, 1] 398 buffer.pushFront(3); // [3, 2, 1] 399 buffer.pushFront(4); // [4, 3, 2, 1] 400 buffer.pushFront(5); // [5, 4, 3, 2, 1] 401 402 assert(buffer.capacity == 5); 403 buffer.reserve(10); 404 assert(equal(buffer, [5, 4, 3, 2, 1])); 405 } 406 407 unittest 408 { 409 import std.algorithm; 410 411 auto buffer = RingBuffer!int(5); 412 413 buffer.pushBack(1); // [1] 414 buffer.pushBack(2); // [1, 2] 415 buffer.pushBack(3); // [1, 2, 3] 416 buffer.pushBack(4); // [1, 2, 3, 4] 417 buffer.pushBack(5); // [1, 2, 3, 4, 5] 418 419 assert(buffer.capacity == 5); 420 assert(equal(buffer, [1, 2, 3, 4, 5])); 421 buffer.reserve(10); 422 assert(equal(buffer, [1, 2, 3, 4, 5])); 423 } 424 425 /// The size of the underlying array may be increased. 426 unittest 427 { 428 import std.algorithm; 429 430 auto buffer = RingBuffer!int(5); 431 432 buffer.pushBack(1); // [1] 433 buffer.pushBack(2); // [1, 2] 434 buffer.pushBack(3); // [1, 2, 3] 435 buffer.pushBack(4); // [1, 2, 3, 4] 436 buffer.pushBack(5); // [1, 2, 3, 4, 5] 437 // elements are no longer linear in memory 438 buffer.pushBack(6); // [2, 3, 4, 5, 6] 439 440 assert(buffer.capacity == 5); 441 buffer.reserve(10); 442 assert(equal(buffer, [2, 3, 4, 5, 6])); 443 } 444 445 446 /// An array-based implementation of a ring buffer. 447 struct BoundedStack(T, size_t staticBufferSize = size_t.max) 448 { 449 private: 450 451 static if (staticBufferSize < size_t.max) 452 T[staticBufferSize] _buffer; 453 else 454 T[] _buffer; 455 ptrdiff_t _stackPtr = -1; 456 457 public: 458 459 static if (staticBufferSize == size_t.max) 460 { 461 this(size_t bufferSize) pure nothrow @safe 462 { 463 this._buffer = new T[bufferSize]; 464 } 465 466 467 this(T[] buffer) pure nothrow @safe 468 { 469 this._buffer = buffer; 470 } 471 472 473 void reserve(size_t capacity) pure nothrow @safe 474 { 475 if (this.capacity < capacity) 476 this._buffer.length = capacity; 477 } 478 } 479 480 481 static if (staticBufferSize < size_t.max) 482 enum bufferSize = _buffer.length; 483 else 484 @property size_t bufferSize() const pure nothrow @safe @nogc 485 { 486 return _buffer.length; 487 } 488 489 alias capacity = bufferSize; 490 491 492 @property T[] buffer() pure nothrow @safe @nogc 493 { 494 return _buffer[0 .. length]; 495 } 496 497 498 T[] opIndex() pure nothrow @safe @nogc 499 { 500 return this.buffer; 501 } 502 503 504 @property BoundedStack!(T, staticBufferSize) save() const pure nothrow @trusted @nogc 505 { 506 return cast(typeof(return)) this; 507 } 508 509 510 @property bool empty() const pure nothrow @safe @nogc 511 { 512 return _stackPtr < 0 || bufferSize == 0; 513 } 514 515 516 @property size_t length() const pure nothrow @safe @nogc 517 { 518 return _stackPtr + 1; 519 } 520 521 522 @property auto ref T front() pure nothrow @safe @nogc 523 { 524 assert(!empty, "Attempting to fetch the front of an empty RingBuffer"); 525 526 return _buffer[_stackPtr]; 527 } 528 529 530 @property void front(T newFront) 531 { 532 assert(!empty, "Attempting to assign to the front of an empty RingBuffer"); 533 534 this.front() = newFront; 535 } 536 537 538 void popFront() pure nothrow @safe @nogc 539 { 540 assert(!empty, "Attempting to popFront an empty RingBuffer"); 541 542 --_stackPtr; 543 } 544 545 546 void clear() pure nothrow @safe @nogc 547 { 548 _stackPtr = -1; 549 } 550 551 552 void pushFront(T value) pure nothrow @safe @nogc 553 { 554 assert(capacity > 0, "Attempting to pushFront an zero-sized RingBuffer"); 555 assert(length < capacity, "Attempting to pushFront to a full RingBuffer"); 556 557 ++_stackPtr; 558 front = value; 559 } 560 561 562 void pushFront(T[] values) pure nothrow @safe @nogc 563 { 564 assert(capacity > 0, "Attempting to pushFront an zero-sized RingBuffer"); 565 assert(length + values.length <= capacity, "Attempting to pushFront to a too small RingBuffer"); 566 567 _stackPtr += values.length; 568 _buffer[$ - values.length .. $] = values; 569 } 570 571 572 alias put = pushFront; 573 574 575 void opOpAssign(string op = "~=")(T value) pure nothrow @safe @nogc 576 { 577 pushFront(value); 578 } 579 580 581 void opOpAssign(string op = "~=")(T[] values) pure nothrow @safe @nogc 582 { 583 pushFront(values); 584 } 585 } 586 587 unittest 588 { 589 import std.meta; 590 import std.range.primitives; 591 592 alias Element = int; 593 alias DyanmicRB = RingBuffer!Element; 594 alias StaticRB = RingBuffer!(Element, 10); 595 596 static foreach (alias RB; AliasSeq!(DyanmicRB, StaticRB)) 597 { 598 static assert(isInputRange!RB); 599 static assert(isOutputRange!(RB, Element)); 600 static assert(isForwardRange!RB); 601 static assert(is(ElementType!RB == Element)); 602 static assert(hasAssignableElements!RB); 603 static assert(hasLvalueElements!RB); 604 static assert(hasLength!RB); 605 static assert(!isInfinite!RB); 606 } 607 } 608 609 /// 610 unittest 611 { 612 import std.algorithm; 613 614 auto stack = BoundedStack!int(5); 615 616 stack.pushFront(1); 617 stack.pushFront(2); 618 stack.pushFront(3); 619 stack.pushFront(4); 620 stack.pushFront(5); 621 622 assert(stack.front == 5); 623 assert(equal(stack, [5, 4, 3, 2, 1])); 624 } 625 626 /// A custom buffer may be used. 627 unittest 628 { 629 import std.algorithm; 630 631 auto stack = BoundedStack!int(new int[5]); 632 633 stack.pushFront(1); 634 stack.pushFront(2); 635 stack.pushFront(3); 636 stack.pushFront(4); 637 stack.pushFront(5); 638 639 assert(stack.front == 5); 640 assert(equal(stack, [5, 4, 3, 2, 1])); 641 } 642 643 /// Ring buffer may have a static size. 644 unittest 645 { 646 import std.algorithm; 647 648 auto stack = BoundedStack!(int, 5)(); 649 650 stack.pushFront(1); 651 stack.pushFront(2); 652 stack.pushFront(3); 653 stack ~= 4; 654 stack ~= 5; 655 656 assert(stack.front == 5); 657 assert(equal(stack, [5, 4, 3, 2, 1])); 658 } 659 660 661 /// Elements can be removed. 662 unittest 663 { 664 import std.algorithm; 665 666 auto stack = BoundedStack!int(5); 667 668 stack.pushFront(1); 669 stack.pushFront(2); 670 stack.pushFront(3); 671 stack.pushFront(4); 672 stack.pushFront(5); 673 stack.popFront(); 674 stack.popFront(); 675 stack.popFront(); 676 stack.popFront(); 677 678 assert(stack.front == 1); 679 } 680 681 /// The stack can be used as an output range. 682 unittest 683 { 684 import std.algorithm; 685 import std.range; 686 687 auto stack = BoundedStack!int(5); 688 689 iota(5).copy(&stack); 690 691 assert(equal(stack, iota(5).retro)); 692 } 693 694 /// The stack can be resized but that may relocate the underlying buffer. 695 unittest 696 { 697 import std.algorithm; 698 699 auto stack = BoundedStack!int(5); 700 701 stack.pushFront(1); 702 stack.pushFront(2); 703 stack.pushFront(3); 704 stack.pushFront(4); 705 stack.pushFront(5); 706 707 assert(stack.front == 5); 708 assert(stack.length == stack.capacity); 709 710 stack.reserve(10); 711 712 stack.pushFront(6); 713 stack.pushFront(7); 714 stack.pushFront(8); 715 stack.pushFront(9); 716 stack.pushFront(10); 717 718 assert(stack.front == 10); 719 } 720 721 /// The buffer may be accessed directly 722 unittest 723 { 724 auto stack = BoundedStack!int(5); 725 726 stack.pushFront(1); 727 stack.pushFront(2); 728 stack.pushFront(3); 729 stack.pushFront(4); 730 stack.pushFront(5); 731 732 assert(stack.buffer == stack[]); 733 assert(stack[] == [1, 2, 3, 4, 5]); 734 } 735 736 unittest 737 { 738 auto stack = BoundedStack!int(5); 739 740 stack.pushFront(1); 741 stack.pushFront(2); 742 stack.pushFront(3); 743 stack.pushFront(4); 744 stack.pushFront(5); 745 746 assert(stack[] == [1, 2, 3, 4, 5]); 747 748 stack.clear(); 749 750 assert(stack[] == []); 751 } 752 753 static class StaticLRUCacheMiss : Exception 754 { 755 import std.exception : basicExceptionCtors; 756 757 /// 758 mixin basicExceptionCtors; 759 } 760 761 struct StaticLRUCache(Key, Value, size_t cacheSize) 762 { 763 static assert(cacheSize > 0, "zero-sized " ~ typeof(this).stringof ~ " is not allowed"); 764 765 private static struct Item 766 { 767 Key key; 768 Value value; 769 Item* next; 770 } 771 772 private Item* queue; 773 private Item[cacheSize] cache; 774 private size_t numCached; 775 776 777 /// Returns true if key is in the cache. 778 bool has(const Key key) const pure nothrow @safe @nogc 779 { 780 return findItem(key) !is null; 781 } 782 783 784 /// Returns the cached value if key is in the cache; throws otherwise. 785 /// This marks the item as recently used. 786 /// 787 /// Throws: StaticLRUCacheMiss if key is not in the cache. 788 Value get(const Key key) pure @safe 789 { 790 Item* prevItemPtr; 791 auto itemPtr = findItem(key, prevItemPtr); 792 793 if (itemPtr is null) 794 throw new StaticLRUCacheMiss("requested key is not in the cache"); 795 796 moveToFront(itemPtr, prevItemPtr); 797 798 return itemPtr.value; 799 } 800 801 /// ditto 802 alias opIndex = get; 803 804 805 /// Returns a pointer to the cached value if key is in the cache; null 806 /// otherwise. The pointer may get invalidated by updating the cache. 807 /// This marks the item as recently used. 808 Value* find(const Key key) pure nothrow @safe @nogc 809 { 810 Item* prevItemPtr; 811 auto itemPtr = findItem(key, prevItemPtr); 812 813 if (itemPtr is null) 814 return null; 815 816 moveToFront(itemPtr, prevItemPtr); 817 818 return &itemPtr.value; 819 } 820 821 /// ditto 822 const(Value)* find(const Key key) const pure nothrow @safe @nogc 823 { 824 auto itemPtr = findItem(key); 825 826 if (itemPtr is null) 827 return null; 828 829 return &itemPtr.value; 830 } 831 832 /// ditto 833 template opBinaryRight(string op) 834 { 835 static if (op == "in") 836 alias opBinaryRight = find; 837 } 838 839 840 /// Cache value at key. Updates the value if key is already in the cache. 841 /// This marks the item as recently used. 842 ref Value set(Key key, Value value) return pure nothrow @safe @nogc 843 { 844 scope assignValue = delegate (ref Value dest) pure nothrow @safe @nogc { dest = value; }; 845 846 return set(key, assignValue); 847 } 848 849 850 /// ditto 851 ref Value set(Func)(Key key, scope Func update) return 852 if (is(typeof(update(lvalueOf!Value)) == void)) 853 { 854 Item* prevItemPtr; 855 auto itemPtr = findItem(key, prevItemPtr); 856 857 if (itemPtr is null) 858 return insertItem(key, update).value; 859 else 860 return updateItem(itemPtr, update, prevItemPtr).value; 861 } 862 863 /// ditto 864 ref Value opIndexAssign(Value value, Key key) return pure nothrow @safe @nogc 865 { 866 return set(key, value); 867 } 868 869 /// ditto 870 ref Value opIndexAssign(Func)(scope Func update, Key key) return 871 if (is(typeof(update(lvalueOf!Value)) == void)) 872 { 873 return set(key, update); 874 } 875 876 877 /// Returns the number of items in the cache. 878 @property size_t length() const pure nothrow @safe @nogc 879 { 880 return numCached; 881 } 882 883 884 /// Iterate over the entries in the cache. This does not count as an 885 /// access. 886 int opApply(scope int delegate(ref inout(Key), ref inout(Value)) yield) inout 887 { 888 int result; 889 inout(Item)* current = queue; 890 891 while (current !is null) 892 { 893 result = yield(current.key, current.value); 894 895 if (result) 896 break; 897 898 current = current.next; 899 } 900 901 return result; 902 } 903 904 /// ditto 905 int opApply(scope int delegate(ref inout(Value)) yield) inout 906 { 907 int result; 908 inout(Item)* current = queue; 909 910 while (current !is null) 911 { 912 result = yield(current.value); 913 914 if (result) 915 break; 916 917 current = current.next; 918 } 919 920 return result; 921 } 922 923 924 ref auto byKeyValue() return pure nothrow @safe @nogc 925 { 926 alias KeyValue = Tuple!( 927 Key, "key", 928 Value, "value", 929 ); 930 931 static struct ByKeyValue 932 { 933 Item* current; 934 935 void popFront() pure nothrow @safe 936 { 937 assert(!empty, "Attempting to popFront an empty " ~ typeof(this).stringof); 938 939 current = current.next; 940 } 941 942 943 @property KeyValue front() pure nothrow @safe 944 { 945 assert(!empty, "Attempting to fetch the front of an empty " ~ typeof(this).stringof); 946 947 return KeyValue(current.key, current.value); 948 } 949 950 951 @property bool empty() const pure nothrow @safe 952 { 953 return current is null; 954 } 955 } 956 957 return ByKeyValue(queue); 958 } 959 960 961 ref auto byKey() return pure nothrow @safe @nogc 962 { 963 static struct ByKey 964 { 965 Item* current; 966 967 void popFront() pure nothrow @safe 968 { 969 assert(!empty, "Attempting to popFront an empty " ~ typeof(this).stringof); 970 971 current = current.next; 972 } 973 974 975 @property Key front() pure nothrow @safe 976 { 977 assert(!empty, "Attempting to fetch the front of an empty " ~ typeof(this).stringof); 978 979 return current.key; 980 } 981 982 983 @property bool empty() const pure nothrow @safe 984 { 985 return current is null; 986 } 987 } 988 989 return ByKey(queue); 990 } 991 992 993 ref auto byValue() return pure nothrow @safe @nogc 994 { 995 static struct ByValue 996 { 997 Item* current; 998 999 void popFront() pure nothrow @safe 1000 { 1001 assert(!empty, "Attempting to popFront an empty " ~ typeof(this).stringof); 1002 1003 current = current.next; 1004 } 1005 1006 1007 @property Value front() pure nothrow @safe 1008 { 1009 assert(!empty, "Attempting to fetch the front of an empty " ~ typeof(this).stringof); 1010 1011 return current.value; 1012 } 1013 1014 1015 @property bool empty() const pure nothrow @safe 1016 { 1017 return current is null; 1018 } 1019 } 1020 1021 return ByValue(queue); 1022 } 1023 1024 1025 // Insert item at the beginning of the queue. 1026 private ref Item insertItem(Func)(ref Key key, scope Func update) return 1027 if (is(typeof(update(lvalueOf!Value)) == void)) 1028 { 1029 if (numCached < cacheSize) 1030 { 1031 auto itemPtr = staticNewItem(key); 1032 moveToFront(itemPtr); 1033 update(itemPtr.value); 1034 1035 return *itemPtr; 1036 } 1037 else 1038 { 1039 Item* secondLRUItem; 1040 auto lruItem = findLeastRecentlyUsedItem(secondLRUItem); 1041 1042 if (lruItem !is null) 1043 { 1044 lruItem.key = key; 1045 update(lruItem.value); 1046 moveToFront(lruItem, secondLRUItem); 1047 1048 return *lruItem; 1049 } 1050 else 1051 { 1052 assert(0, "unreachable"); 1053 } 1054 } 1055 } 1056 1057 1058 private Item* staticNewItem(ref Key key) pure nothrow @safe @nogc 1059 out (item; item !is null) 1060 { 1061 auto itemPtr = &cache[numCached++]; 1062 1063 *itemPtr = Item(key); 1064 1065 return itemPtr; 1066 } 1067 1068 1069 // Update item and move to the beginning of the queue. 1070 private ref Item updateItem(Func)(Item* itemPtr, scope Func update, Item* prevItemPtr) return 1071 if (is(typeof(update(lvalueOf!Value)) == void)) 1072 in (itemPtr !is null) 1073 { 1074 update(itemPtr.value); 1075 moveToFront(itemPtr, prevItemPtr); 1076 1077 return *itemPtr; 1078 } 1079 1080 1081 private void moveToFront(Item* item, Item* prevItem = null) pure nothrow @safe @nogc 1082 in (item !is null) 1083 { 1084 if (prevItem !is null) 1085 prevItem.next = item.next; 1086 1087 item.next = queue; 1088 queue = item; 1089 } 1090 1091 1092 private Item* findLeastRecentlyUsedItem(out Item* secondLRUItem) pure nothrow @safe @nogc 1093 { 1094 Item* prevItem; 1095 1096 return findBy!"a.next is null"(prevItem); 1097 } 1098 1099 1100 private const(Item)* findItem(const ref Key key) const pure nothrow @safe @nogc 1101 { 1102 const(Item)* prevItem; 1103 1104 return findBy!"a.key == b"(prevItem, key); 1105 } 1106 1107 1108 private Item* findItem(const ref Key key, out Item* prevItem) pure nothrow @safe @nogc 1109 { 1110 return findBy!"a.key == b"(prevItem, key); 1111 } 1112 1113 1114 private inout(Item)* findBy(alias pred, Args...)(out inout(Item)* prevItem, Args args) inout 1115 { 1116 import std.functional; 1117 1118 static if (Args.length == 0) 1119 alias _pred = unaryFun!pred; 1120 else static if (Args.length == 1) 1121 alias _pred = binaryFun!pred; 1122 else 1123 alias _pred = pred; 1124 1125 inout(Item)* current = queue; 1126 size_t i; 1127 while (current !is null && i <= numCached) 1128 { 1129 if (_pred(current, args)) 1130 return current; 1131 1132 prevItem = current; 1133 current = current.next; 1134 ++i; 1135 } 1136 assert(i <= numCached, "loop detected"); 1137 1138 return null; 1139 } 1140 } 1141 1142 unittest 1143 { 1144 import std.algorithm.comparison : equal; 1145 1146 enum cacheSize = 5; 1147 StaticLRUCache!(int, string, cacheSize) cache; 1148 1149 // Fill the cache 1150 cache[1] = "Apple"; 1151 cache[2] = "Banana"; 1152 cache[3] = "Coconut"; 1153 assert(cache.length == 3); 1154 cache[4] = "Dragonfruit"; 1155 cache[5] = "Eggplant"; 1156 // Cache is full; the next insertion drops the least-recently used item 1157 // which is (1, "Apple"). 1158 assert(cache.length == cacheSize); 1159 cache[6] = "Asparagus"; 1160 1161 assert(equal(cache.byKeyValue, [ 1162 tuple(6, "Asparagus"), 1163 tuple(5, "Eggplant"), 1164 tuple(4, "Dragonfruit"), 1165 tuple(3, "Coconut"), 1166 tuple(2, "Banana"), 1167 ])); 1168 1169 // Acessing (read or write) an item marks it as recently accessed, 1170 // i.e. it is pushed to the front. 1171 cache[3] = "Carrot"; // write 1172 assert(cache[2] == "Banana"); // read-only 1173 assert(4 in cache); // read/write (returns a pointer to the value) 1174 1175 assert(equal(cache.byKeyValue, [ 1176 tuple(4, "Dragonfruit"), 1177 tuple(2, "Banana"), 1178 tuple(3, "Carrot"), 1179 tuple(6, "Asparagus"), 1180 tuple(5, "Eggplant"), 1181 ])); 1182 1183 // An udpate can be avoided by using `has` or a `const` object. 1184 assert(cache.has(6)); 1185 assert(6 in cast(const) cache); 1186 1187 assert(equal(cache.byKeyValue, [ 1188 tuple(4, "Dragonfruit"), 1189 tuple(2, "Banana"), 1190 tuple(3, "Carrot"), 1191 tuple(6, "Asparagus"), 1192 tuple(5, "Eggplant"), 1193 ])); 1194 }