1 module tame.queue; 2 import std.traits : hasMember, hasIndirections; 3 4 /* 5 * Simple queue implemented as a singly linked list with a tail pointer. 6 * 7 * Needed in some D:YAML code that needs a queue-like structure without too much 8 * reallocation that goes with an array. 9 * 10 * Allocations are non-GC and are damped by a free-list based on the nodes 11 * that are removed. Note that elements lifetime must be managed 12 * outside. 13 */ 14 struct Queue(T) if (!hasMember!(T, "__xdtor")) { 15 private: 16 17 // Linked list node containing one element and pointer to the next node. 18 struct Node { 19 T value; 20 Node* next; 21 } 22 23 // Start of the linked list - first element added in time (end of the queue). 24 Node* first_; 25 // Last element of the linked list - last element added in time (start of the queue). 26 Node* last_; 27 // free-list 28 Node* stock; 29 30 // Length of the queue. 31 size_t len; 32 33 // allocate a new node or recycle one from the stock. 34 Node* make(T value, Node* theNext = null) @trusted nothrow @nogc { 35 import std.experimental.allocator : make; 36 import std.experimental.allocator.mallocator : Mallocator; 37 38 Node* result; 39 if (stock !is null) { 40 result = stock; 41 stock = result.next; 42 result.value = value; 43 result.next = theNext; 44 } else { 45 result = Mallocator.instance.make!Node(value, theNext); 46 // GC can dispose T managed member if it thinks they are no used... 47 static if (hasIndirections!T) { 48 import core.memory : GC; 49 50 GC.addRange(result, Node.sizeof); 51 } 52 } 53 return result; 54 } 55 56 // free the stock of available free nodes. 57 void freeStock() @trusted @nogc nothrow { 58 import std.experimental.allocator.mallocator : Mallocator; 59 60 while (stock) { 61 Node* toFree = stock; 62 stock = stock.next; 63 static if (hasIndirections!T) { 64 import core.memory : GC; 65 66 GC.removeRange(toFree); 67 } 68 Mallocator.instance.deallocate((cast(ubyte*)toFree)[0 .. Node.sizeof]); 69 } 70 } 71 72 public: 73 74 @disable void opAssign(ref Queue); 75 @disable bool opEquals(ref Queue); 76 @disable int opCmp(ref Queue); 77 78 @safe nothrow: 79 this(this) @nogc { 80 auto node = first_; 81 first_ = null; 82 last_ = null; 83 while (node !is null) { 84 Node* newLast = make(node.value); 85 if (last_ !is null) 86 last_.next = newLast; 87 if (first_ is null) 88 first_ = newLast; 89 last_ = newLast; 90 node = node.next; 91 } 92 } 93 94 ~this() { 95 freeStock(); 96 stock = first_; 97 freeStock(); 98 } 99 100 /// Returns a forward range iterating over this queue. 101 auto range() { 102 static struct Result { 103 private Node* cursor; 104 105 @safe pure nothrow @nogc: 106 void popFront() { 107 cursor = cursor.next; 108 } 109 110 ref T front() 111 in (cursor) { 112 return cursor.value; 113 } 114 115 bool empty() { 116 return cursor is null; 117 } 118 } 119 120 return Result(first_); 121 } 122 123 /// Push a new item to the queue. 124 void push(T item) @nogc { 125 Node* newLast = make(item); 126 if (last_ !is null) 127 last_.next = newLast; 128 if (first_ is null) 129 first_ = newLast; 130 last_ = newLast; 131 ++len; 132 } 133 134 /// Insert a new item putting it to specified index in the linked list. 135 void insert(T item, const size_t idx) 136 in (idx <= len) { 137 if (idx == 0) { 138 first_ = make(item, first_); 139 ++len; 140 } // Adding before last added element, so we can just push. 141 else if (idx == len) { 142 push(item); 143 } else { 144 // Get the element before one we're inserting. 145 Node* current = first_; 146 foreach (i; 1 .. idx) 147 current = current.next; 148 149 assert(current); 150 // Insert a new node after current, and put current.next behind it. 151 current.next = make(item, current.next); 152 ++len; 153 } 154 } 155 156 /// Returns: The next element in the queue and remove it. 157 T pop() 158 in (!empty, "Trying to pop an element from an empty queue") { 159 T result = peek(); 160 161 Node* oldStock = stock; 162 Node* old = first_; 163 first_ = first_.next; 164 165 // start the stock from the popped element 166 stock = old; 167 old.next = null; 168 // add the existing "old" stock to the new first stock element 169 if (oldStock !is null) 170 stock.next = oldStock; 171 172 if (--len == 0) { 173 assert(first_ is null); 174 last_ = null; 175 } 176 177 return result; 178 } 179 180 pure @nogc: 181 /// Returns: The next element in the queue. 182 ref inout(T) peek() inout 183 in (!empty, "Trying to peek at an element in an empty queue") 184 do { 185 return first_.value; 186 } 187 188 /// Returns: true of the queue empty, false otherwise. 189 bool empty() const { 190 return first_ is null; 191 } 192 193 /// Returns: The number of elements in the queue. 194 size_t length() const { 195 return len; 196 } 197 } 198 199 @safe nothrow unittest { 200 auto queue = Queue!int(); 201 assert(queue.empty); 202 foreach (i; 0 .. 65) { 203 queue.push(5); 204 assert(queue.pop() == 5); 205 assert(queue.empty); 206 assert(queue.len == 0); 207 } 208 209 auto arr = [1, -1, 2, -2, 3, -3, 4, -4, 5, -5]; 210 foreach (i; arr) { 211 queue.push(i); 212 } 213 214 arr = 42 ~ arr[0 .. 3] ~ 42 ~ arr[3 .. $] ~ 42; 215 queue.insert(42, 3); 216 queue.insert(42, 0); 217 queue.insert(42, queue.length); 218 219 int[] arr2; 220 while (!queue.empty) { 221 arr2 ~= queue.pop(); 222 } 223 224 assert(arr == arr2); 225 }