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