1 module collections.sortedlist;
2 
3 import collections.commons;
4 import std.functional : binaryFun;
5 
6 /**
7  * Implements a sorted list of type T.
8  * The container ensures that all of it's elements are ordered after any insertion.
9  * Has some optimization to stop searching after a given value has passed.
10  * `cmp` can set both the direction of the array, and what parameters should be tested.
11  * If `allowDuplicates` set false, the collection will act like a sorted set over an array, and won't allow any insertion of preexisting values.
12  * Important: The elements stored within this container must have opCmp override with attributes `@safe`, `nothrow`, `pure`.
13  */
14 public struct SortedList(E, alias cmp = "a < b", bool allowDuplicates = true, alias equal = "a == b") {
15 	private E[]			_array;		///The array where the elements are stored.
16 	private size_t		begin;		///The position of the current front iteration.
17 	private size_t		end;		///The position of the current back iteration.
18 	/**
19 	 * Constructs a sorted list out from an array.
20 	 */
21 	public this(E[] _array) @safe nothrow pure {
22 		this._array.reserve = _array.length;
23         foreach (E key; _array) {
24             put(key);
25         }
26         assert(this._array.length == _array.length);
27         assert(this._array.length == end);
28 	}
29 	/**
30 	 * Constructs a sorted list out from a range.
31 	 */
32 	public this(Range)(Range src) {
33 		foreach (E key; src) {
34             put(key);
35         }
36         //assert(this._array.length == _array.length);
37         assert(this._array.length == end);
38 	}
39     /**
40 	 * Adds a new element while keeping the order intact.
41 	 *
42 	 * TO DO: Maybe even more optimizations? Maybe look where the element would fit, then shift that position?
43 	 */
44 	void put(E a) @safe nothrow pure {
45 		static if(!allowDuplicates){
46 			if(has(a)) return;
47 		}
48 		_array.length = _array.length + 1;
49 		for(sizediff_t i = _array.length - 2 ; i >= 0 ; i--) {
50 			E b = _array[i];
51 			if(binaryFun!cmp(a, b)) { //first position found
52 				_array[i + 1] = a;
53 				end++;
54 				return;
55 			} else {
56 				_array[i + 1] = b;
57 			}
58 		}
59 		_array[0] = a;
60         end++;
61 	}
62 	/**
63 	 * Removes the n-th element while keeping the order intact.
64 	 * Returns the removed element.
65 	 */
66 	E remove(size_t n) @safe nothrow pure {
67 		E result = _array[n];
68 		if(n != _array.length) {
69 			for ( ; n < _array.length - 1 ; n++) {
70 				_array[n] = _array[n + 1];
71 			}
72 		}
73 		_array.length = _array.length - 1;
74 		end--;
75 		return result;
76     }
77 	static if(!allowDuplicates) {
78 		/**
79 		 * Removes the element which is equal with the given one if the template is set to not allow duplicates.
80 		 * Returns the removed element, or E.init if not found.
81 		 */
82 		E removeByElem(E a) @safe nothrow pure {
83 			foreach_reverse(i, b; _array) {
84 				if(binaryFun!equal(a, b)) return this.remove(i);
85 				else if(binaryFun!cmp(a, b)) break;
86 			}
87 			return E.init;
88 		}
89 		/**
90 		 * Returns whether the set has the given element.
91 		 */
92 		bool has(E a) @nogc @safe nothrow pure {
93 			foreach_reverse(i, b; _array) {
94 				if(binaryFun!equal(a,b)) return true;
95 				else if(binaryFun!cmp(a,b)) break;
96 			}
97 			return false;
98 		}
99 		/**
100 		 * Returns the index of the given element, or throws an ElementNotFoundException if not found.
101 		 */
102 		size_t which(E a) @safe pure {
103 			foreach_reverse(i, b; _array) {
104 				if(binaryFun!equal(a,b)) return i;
105 				else if(binaryFun!cmp(a,b)) break;
106 			}
107 			throw new ElementNotFoundException("Element not found!");
108 		}
109 		/**
110 	 	 * Returns a slice from the list, but the slicing is done by element.
111 		 * Search is done in a way that if either cmp or equals is true to an element, its position will be chosen
112 		 * for the slice.
113 	 	 */
114 		SortedList!(E, cmp, allowDuplicates, equal) sliceByElem(E from, E to) @safe pure {
115 			size_t f, t = _array.length;
116 			E a = from;
117 			foreach_reverse(size_t i ,E b; _array) {
118 				if(binaryFun!cmp(a,b) || binaryFun!equal(a,b)){ 
119 					f = i;
120 					break;
121 				}
122 			}
123 			a = to;
124 			foreach_reverse(size_t i ,E b; _array) {
125 				if(binaryFun!cmp(a,b) || binaryFun!equal(a,b)){ 
126 					t = i;
127 					break;
128 				}
129 			}
130 			return opSlice(f, t);
131 		}
132 	}
133 	/**
134 	 * Returns a copy of this struct.
135 	 */
136 	@property auto save() @nogc @safe nothrow pure {
137 		return this;
138 	}
139 	/**
140 	 * Returns the element at the front.
141 	 */
142 	@property E front() @nogc @safe nothrow pure {
143 		return _array[begin];
144 	}
145 	/+/**
146 	 * Returns the element at the front.
147 	 */
148 	@property ref E frontRef() @nogc @safe nothrow pure {
149 		return _array[begin];
150 	}+/
151 	/**
152 	 * Returns the element at the back.
153 	 */
154 	@property E back() @nogc @safe nothrow pure {
155 		return _array[end - 1];
156 	}
157 	/+/**
158 	 * Returns the element at the back.
159 	 */
160 	@property ref E backRef() @nogc @safe nothrow pure {
161 		return _array[end - 1];
162 	}+/
163 	/**
164 	 * Returns the element at begin and increments the position by one.
165 	 */
166 	E moveFront() @nogc @safe nothrow pure {
167 		E result = _array[begin];
168 		popFront();
169 		return result;
170 	}
171 	/**
172 	 * Increments the front iteration position by one
173 	 */
174 	void popFront() @nogc @safe nothrow pure {
175 		if(begin < end) begin++;
176 	}
177 	/**
178 	 * Decrements the back iteration position by one
179 	 */
180 	void popBack() @nogc @safe nothrow pure {
181 		if(begin < end) end--;
182 	}
183 	/**
184 	 * Returns true when the end of the list have been reached.
185 	 */
186 	@property bool empty() @nogc @safe nothrow pure {
187 		return begin == end;
188 	}
189 	/**
190 	 * Returns the n-th element.
191 	 */
192 	E opIndex(size_t n) @nogc @safe nothrow pure {
193 		return _array[n];
194 	}
195 	/**
196 	 * Moves to the n-th position and returns the element of that position.
197 	 */
198 	E moveAt(size_t n) @nogc @safe nothrow pure {
199 		begin = n;
200 		return _array[begin];
201 	}
202 	/**
203 	 * Returns the length of the list.
204 	 */
205 	@property size_t length() @nogc @safe nothrow pure {
206 		return _array.length;
207 	}
208 	alias opDollar = length;
209 	/**
210 	 * Sets the reserve of the underlying array and returns the current reserve.
211 	 */
212 	@property size_t reserve(size_t amount) @safe pure nothrow {
213 		return _array.reserve(amount);
214 	}
215 	/**
216 	 * Returns a slice from the list.
217 	 */
218 	SortedList!(E, cmp, allowDuplicates, equal) opSlice(size_t i0, size_t i1) @safe nothrow pure {
219 		return SortedList!(E, cmp, allowDuplicates, equal)(_array[i0..i1]);
220 	}
221 	/**
222 	 * Returns a copy of the underlying array.
223 	 */
224 	@property E[] arrayOf() @safe nothrow pure const {
225 		return _array.dup;
226 	}
227 	string toString() {
228 		import std.conv : to;
229 		return to!string(_array);
230 	}
231 }
232 
233 @safe unittest {
234 	import std.stdio : writeln;
235 	alias SortedIntList = SortedList!(int, "a < b", true);
236 	alias SortedIntSet = SortedList!(int, "a < b", false);
237 	SortedIntList sil;
238 	sil.put(5);
239 	assert(sil.arrayOf == [5], sil.toString);
240 	sil.put(3);
241 	assert(sil.arrayOf == [5, 3], sil.toString);
242 	sil.put(7);
243 	assert(sil.arrayOf == [7, 5, 3], sil.toString);
244 	sil.put(-1);
245 	assert(sil.arrayOf == [7, 5, 3, -1], sil.toString);
246 	sil.remove(2);
247 	assert(sil.arrayOf == [7, 5, -1], sil.toString);
248 	sil.put(2);
249 	assert(sil.arrayOf == [7 ,5, 2, -1], sil.toString);
250 	SortedIntSet sis;
251 	sis.put(5);
252 	assert(sis.arrayOf == [5], sis.toString);
253 	sis.put(5);
254 	assert(sis.arrayOf == [5], sis.toString);
255 	sis.put(3);
256 	assert(sis.arrayOf == [5, 3], sis.toString);
257 	sis.put(3);
258 	assert(sis.arrayOf == [5, 3], sis.toString);
259 	sis.put(7);
260 	assert(sis.arrayOf == [7, 5, 3], sis.toString);
261 	sis.put(7);
262 	assert(sis.arrayOf == [7, 5, 3], sis.toString);
263 	sis.put(-2);
264 	assert(sis.arrayOf == [7, 5, 3, -2], sis.toString);
265 	sis.put(11);
266 	assert(sis.arrayOf == [11, 7, 5, 3, -2], sis.toString);
267 	assert(!sis.has(12), sis.toString);
268 	assert(sis.has(11), sis.toString);
269 	sis.remove(0);
270 	assert(!sis.has(11), sis.toString);
271 	assert(sis.arrayOf == [7, 5, 3, -2], sis.toString);
272 	sis.put(1);
273 	assert(sis.arrayOf == [7, 5, 3, 1, -2], sis.toString);
274 	sis.put(1);
275 	assert(sis.arrayOf == [7, 5, 3, 1, -2], sis.toString);
276 	foreach(e; sil) {
277 		writeln(e);
278 	}
279 	foreach(e; sis) {
280 		writeln(e);
281 	}
282 }
283 
284 @safe unittest {
285 	import std.stdio : writeln;
286 	alias SortedIntList = SortedList!(int, "a > b", true);
287 	alias SortedIntSet = SortedList!(int, "a > b", false);
288 	SortedIntList sil;
289 	sil.put(5);
290 	assert(sil.arrayOf == [5], sil.toString);
291 	sil.put(3);
292 	assert(sil.arrayOf == [3, 5], sil.toString);
293 	sil.put(7);
294 	assert(sil.arrayOf == [3, 5, 7], sil.toString);
295 	sil.put(-1);
296 	assert(sil.arrayOf == [-1, 3, 5, 7], sil.toString);
297 	sil.remove(2);
298 	assert(sil.arrayOf == [-1, 3, 7], sil.toString);
299 	sil.put(2);
300 	assert(sil.arrayOf == [-1,2, 3, 7], sil.toString);
301 	SortedIntSet sis;
302 	sis.put(5);
303 	assert(sis.arrayOf == [5], sis.toString);
304 	sis.put(5);
305 	assert(sis.arrayOf == [5], sis.toString);
306 	sis.put(3);
307 	assert(sis.arrayOf == [3, 5], sis.toString);
308 	sis.put(3);
309 	assert(sis.arrayOf == [3, 5], sis.toString);
310 	sis.put(7);
311 	assert(sis.arrayOf == [3, 5, 7], sis.toString);
312 	sis.put(7);
313 	assert(sis.arrayOf == [3, 5, 7], sis.toString);
314 	sis.put(-2);
315 	assert(sis.arrayOf == [-2, 3, 5, 7], sis.toString);
316 	sis.put(11);
317 	assert(sis.arrayOf == [-2, 3, 5, 7, 11], sis.toString);
318 	assert(!sis.has(12), sis.toString);
319 	assert(sis.has(11), sis.toString);
320 	sis.remove(0);
321 	assert(!sis.has(-2), sis.toString);
322 	assert(sis.arrayOf == [3, 5, 7, 11], sis.toString);
323 	sis.put(1);
324 	assert(sis.arrayOf == [1, 3, 5, 7, 11], sis.toString);
325 	sis.put(1);
326 	assert(sis.arrayOf == [1, 3, 5, 7, 11], sis.toString);
327 	foreach(e; sil) {
328 		writeln(e);
329 	}
330 	foreach(e; sis) {
331 		writeln(e);
332 	}
333 }