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 }