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  */
13 public struct SortedList(E, alias cmp = "a < b", bool allowDuplicates = true, alias equal = "a == b") {
14 	private E[]			_array;		///The array where the elements are stored.
15 	private size_t		begin;		///The position of the current front iteration.
16 	private size_t		end;		///The position of the current back iteration.
17 	/**
18 	 * Constructs a sorted list out from an array.
19 	 */
20 	public this(E[] _array) @safe nothrow pure {
21 		this._array.reserve = _array.length;
22         foreach (E key; _array) {
23             put(key);
24         }
25         assert(this._array.length == _array.length);
26         assert(this._array.length == end);
27 	}
28 	/**
29 	 * Constructs a sorted list out from a range.
30 	 */
31 	public this(Range)(Range src) {
32 		foreach (E key; src) {
33             put(key);
34         }
35         //assert(this._array.length == _array.length);
36         assert(this._array.length == end);
37 	}
38     /**
39 	 * Adds a new element while keeping the order intact.
40 	 *
41 	 * TO DO: Maybe even more optimizations? Maybe look where the element would fit, then shift that position?
42 	 */
43 	E put(E a) @safe nothrow pure {
44 		static if(!allowDuplicates){
45 			if(has(a)) return E.init;
46 		}
47 		_array.length = _array.length + 1;
48 		for(sizediff_t i = _array.length - 2 ; i >= 0 ; i--) {
49 			E b = _array[i];
50 			if(binaryFun!cmp(a, b)) { //first position found
51 				_array[i + 1] = a;
52 				end++;
53 				return a;
54 			} else {
55 				_array[i + 1] = b;
56 			}
57 		}
58 		_array[0] = a;
59         end++;
60 		return a;
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 		auto opBinaryRight(string op)(const E key) @safe pure nothrow {
100 			static if (op == "in") {
101 				return has(key);
102 			} else static assert(0, "Operator not supported!");
103 		}
104 		/**
105 		 * Returns the amount of elements found in the set.
106 		 */
107 		size_t hasRange(R)(R range) @nogc @safe pure nothrow {
108 			size_t result;
109 			foreach (key; range) {
110 				if(has(key)) result++;
111 			}
112 			return result;
113 		}
114 		/**
115 		 * Returns the index of the given element, or throws an ElementNotFoundException if not found.
116 		 */
117 		size_t which(E a) @safe pure {
118 			foreach_reverse(i, b; _array) {
119 				if(binaryFun!equal(a,b)) return i;
120 				else if(binaryFun!cmp(a,b)) break;
121 			}
122 			throw new ElementNotFoundException("Element not found!");
123 		}
124 		/**
125 	 	 * Returns a slice from the list, but the slicing is done by element.
126 		 * Search is done in a way that if either cmp or equals is true to an element, its position will be chosen
127 		 * for the slice.
128 	 	 */
129 		SortedList!(E, cmp, allowDuplicates, equal) sliceByElem(E from, E to) @safe pure {
130 			size_t f, t = _array.length;
131 			E a = from;
132 			foreach_reverse(size_t i ,E b; _array) {
133 				if(binaryFun!cmp(a,b) || binaryFun!equal(a,b)){ 
134 					f = i;
135 					break;
136 				}
137 			}
138 			a = to;
139 			foreach_reverse(size_t i ,E b; _array) {
140 				if(binaryFun!cmp(a,b) || binaryFun!equal(a,b)){ 
141 					t = i;
142 					break;
143 				}
144 			}
145 			return opSlice(f, t);
146 		}
147 		/**
148 		 * Set operators.
149 		 * Enables math operations on sets, like unions and intersections.
150 		 * Could work on ranges in general as long as they implement some basic functions, like iteration.
151 		 */
152 		SortedList!(E, cmp, allowDuplicates, equal) opBinary(string op, R)(R rhs) {
153 			static if(op == "|" || op == "~") {//Union
154 				SortedList!(E, cmp, allowDuplicates, equal) result;
155 				result.end = this.end;
156 				result._array = this._array.dup;
157 				foreach(e ; rhs){
158 					result.put(e);
159 				}
160 				return result;
161 			} else static if(op == "&" || op == "*") {//Intersection
162 				SortedList!(E, cmp, allowDuplicates, equal) result;
163 				foreach(e ; rhs){
164 					if(this.has(e)) result.put(e);
165 				}
166 				return result;
167 			} else static if(op == "-" || op == "/") {//Complement
168 				SortedList!(E, cmp, allowDuplicates, equal) result = SortedList!(E, cmp, allowDuplicates, equal)(this);
169 				foreach(e ; rhs){
170 					result.removeByElem(e);
171 				}
172 				return result;
173 			} else static if(op == "^"){//Difference
174 				SortedList!(E, cmp, allowDuplicates, equal) result = this | rhs;
175 				SortedList!(E, cmp, allowDuplicates, equal) common = this & rhs;
176 				foreach(e ; common){
177 					result.removeByElem(e);
178 				}
179 				return result;
180 			} else static assert(0, "Operator " ~ op ~ "not supported");
181 		}
182 		/**
183 		 * Set operators.
184 		 */
185 		SortedList!(E, cmp, allowDuplicates, equal) opOpAssign(string op)(E value) {
186 			static if(op == "~=") {//Append
187 				put(value);
188 			} else static if(op == "-=" || op == "/=") {
189 				removeByElem(value);
190 			} else static assert(0, "Operator " ~ op ~ "not supported");
191 			return this;
192 		}
193 		/**
194 		 * Set operators.
195 		 */
196 		SortedList!(E, cmp, allowDuplicates, equal) opOpAssign(string op, R)(R range) {
197 			static if(op == "~=" || op == "|=") {//Append
198 				foreach(val; range)
199 					put(val);
200 			} else static if(op == "-=" || op == "/=") {
201 				foreach(val; range)
202 					removeByElem(val);
203 			} else static assert(0, "Operator " ~ op ~ "not supported");
204 			return this;
205 		}
206 	}
207 	/**
208 	 * Returns a copy of this struct.
209 	 */
210 	@property auto save() @nogc @safe nothrow pure {
211 		return this;
212 	}
213 	/**
214 	 * Returns the element at the front.
215 	 */
216 	@property E front() @nogc @safe nothrow pure {
217 		return _array[begin];
218 	}
219 	/+/**
220 	 * Returns the element at the front.
221 	 */
222 	@property ref E frontRef() @nogc @safe nothrow pure {
223 		return _array[begin];
224 	}+/
225 	/**
226 	 * Returns the element at the back.
227 	 */
228 	@property E back() @nogc @safe nothrow pure {
229 		return _array[end - 1];
230 	}
231 	/+/**
232 	 * Returns the element at the back.
233 	 */
234 	@property ref E backRef() @nogc @safe nothrow pure {
235 		return _array[end - 1];
236 	}+/
237 	/**
238 	 * Returns the element at begin and increments the position by one.
239 	 */
240 	E moveFront() @nogc @safe nothrow pure {
241 		E result = _array[begin];
242 		popFront();
243 		return result;
244 	}
245 	/**
246 	 * Increments the front iteration position by one
247 	 */
248 	void popFront() @nogc @safe nothrow pure {
249 		if(begin < end) begin++;
250 	}
251 	/**
252 	 * Decrements the back iteration position by one
253 	 */
254 	void popBack() @nogc @safe nothrow pure {
255 		if(begin < end) end--;
256 	}
257 	/**
258 	 * Returns true when the end of the list have been reached.
259 	 */
260 	@property bool empty() @nogc @safe nothrow pure {
261 		return begin == end;
262 	}
263 	/**
264 	 * Returns the n-th element.
265 	 */
266 	E opIndex(size_t n) @nogc @safe nothrow pure {
267 		return _array[n];
268 	}
269 	/**
270 	 * Moves to the n-th position and returns the element of that position.
271 	 */
272 	E moveAt(size_t n) @nogc @safe nothrow pure {
273 		begin = n;
274 		return _array[begin];
275 	}
276 	/**
277 	 * Returns the length of the list.
278 	 */
279 	@property size_t length() @nogc @safe nothrow pure {
280 		return _array.length;
281 	}
282 	alias opDollar = length;
283 	/**
284 	 * Sets the reserve of the underlying array and returns the current reserve.
285 	 */
286 	@property size_t reserve(size_t amount) @safe pure nothrow {
287 		return _array.reserve(amount);
288 	}
289 	/**
290 	 * Returns a slice from the list.
291 	 */
292 	SortedList!(E, cmp, allowDuplicates, equal) opSlice(size_t i0, size_t i1) @safe nothrow pure {
293 		return SortedList!(E, cmp, allowDuplicates, equal)(_array[i0..i1]);
294 	}
295 	/**
296 	 * Returns a copy of the underlying array.
297 	 */
298 	@property E[] arrayOf() @safe nothrow pure const {
299 		return _array.dup;
300 	}
301 	string toString() {
302 		import std.conv : to;
303 		return to!string(_array);
304 	}
305 }
306 
307 @safe unittest {
308 	import std.stdio : writeln;
309 	alias SortedIntList = SortedList!(int, "a < b", true);
310 	alias SortedIntSet = SortedList!(int, "a < b", false);
311 	SortedIntList sil;
312 	sil.put(5);
313 	assert(sil.arrayOf == [5], sil.toString);
314 	sil.put(3);
315 	assert(sil.arrayOf == [5, 3], sil.toString);
316 	sil.put(7);
317 	assert(sil.arrayOf == [7, 5, 3], sil.toString);
318 	sil.put(-1);
319 	assert(sil.arrayOf == [7, 5, 3, -1], sil.toString);
320 	sil.remove(2);
321 	assert(sil.arrayOf == [7, 5, -1], sil.toString);
322 	sil.put(2);
323 	assert(sil.arrayOf == [7 ,5, 2, -1], sil.toString);
324 	SortedIntSet sis;
325 	sis.put(5);
326 	assert(sis.arrayOf == [5], sis.toString);
327 	sis.put(5);
328 	assert(sis.arrayOf == [5], sis.toString);
329 	sis.put(3);
330 	assert(sis.arrayOf == [5, 3], sis.toString);
331 	sis.put(3);
332 	assert(sis.arrayOf == [5, 3], sis.toString);
333 	sis.put(7);
334 	assert(sis.arrayOf == [7, 5, 3], sis.toString);
335 	sis.put(7);
336 	assert(sis.arrayOf == [7, 5, 3], sis.toString);
337 	sis.put(-2);
338 	assert(sis.arrayOf == [7, 5, 3, -2], sis.toString);
339 	sis.put(11);
340 	assert(sis.arrayOf == [11, 7, 5, 3, -2], sis.toString);
341 	assert(!sis.has(12), sis.toString);
342 	assert(sis.has(11), sis.toString);
343 	sis.remove(0);
344 	assert(!sis.has(11), sis.toString);
345 	assert(sis.arrayOf == [7, 5, 3, -2], sis.toString);
346 	sis.put(1);
347 	assert(sis.arrayOf == [7, 5, 3, 1, -2], sis.toString);
348 	sis.put(1);
349 	assert(sis.arrayOf == [7, 5, 3, 1, -2], sis.toString);
350 	foreach(e; sil) {
351 		writeln(e);
352 	}
353 	foreach(e; sis) {
354 		writeln(e);
355 	}
356 }
357 
358 @safe unittest {
359 	import std.stdio : writeln;
360 	alias SortedIntList = SortedList!(int, "a > b", true);
361 	alias SortedIntSet = SortedList!(int, "a > b", false);
362 	SortedIntList sil;
363 	sil.put(5);
364 	assert(sil.arrayOf == [5], sil.toString);
365 	sil.put(3);
366 	assert(sil.arrayOf == [3, 5], sil.toString);
367 	sil.put(7);
368 	assert(sil.arrayOf == [3, 5, 7], sil.toString);
369 	sil.put(-1);
370 	assert(sil.arrayOf == [-1, 3, 5, 7], sil.toString);
371 	sil.remove(2);
372 	assert(sil.arrayOf == [-1, 3, 7], sil.toString);
373 	sil.put(2);
374 	assert(sil.arrayOf == [-1,2, 3, 7], sil.toString);
375 	SortedIntSet sis;
376 	sis.put(5);
377 	assert(sis.arrayOf == [5], sis.toString);
378 	sis.put(5);
379 	assert(sis.arrayOf == [5], sis.toString);
380 	sis.put(3);
381 	assert(sis.arrayOf == [3, 5], sis.toString);
382 	sis.put(3);
383 	assert(sis.arrayOf == [3, 5], sis.toString);
384 	sis.put(7);
385 	assert(sis.arrayOf == [3, 5, 7], sis.toString);
386 	sis.put(7);
387 	assert(sis.arrayOf == [3, 5, 7], sis.toString);
388 	sis.put(-2);
389 	assert(sis.arrayOf == [-2, 3, 5, 7], sis.toString);
390 	sis.put(11);
391 	assert(sis.arrayOf == [-2, 3, 5, 7, 11], sis.toString);
392 	assert(!sis.has(12), sis.toString);
393 	assert(sis.has(11), sis.toString);
394 	sis.remove(0);
395 	assert(!sis.has(-2), sis.toString);
396 	assert(sis.arrayOf == [3, 5, 7, 11], sis.toString);
397 	sis.put(1);
398 	assert(sis.arrayOf == [1, 3, 5, 7, 11], sis.toString);
399 	sis.put(1);
400 	assert(sis.arrayOf == [1, 3, 5, 7, 11], sis.toString);
401 	foreach(e; sil) {
402 		writeln(e);
403 	}
404 	foreach(e; sis) {
405 		writeln(e);
406 	}
407 }
408 
409 unittest {
410 	//test set operators
411 	alias IntSet = SortedList!(int, "a > b", false, "a == b");
412 	IntSet a = IntSet([1, 3, 5, 7, 9]), b = IntSet([1, 5, 9]), c = IntSet([3, 7]);
413 	IntSet union_ab = a | b, union_ac = a | c, union_bc = b | c;
414 	IntSet intrsctn_ab = a & b, intrsctn_ac = a & c;
415 	IntSet cmplmnt_ab = a - b, cmplmnt_ac = a - c;
416 	IntSet diff_ab = a ^ b, diff_ac = a ^ c, diff_bc = b ^ c;
417 	assert(union_ab.hasRange([1, 3, 5, 7, 9]) == 5);
418 	assert(union_ac.hasRange([1, 3, 5, 7, 9]) == 5);
419 	assert(union_bc.hasRange([1, 3, 5, 7, 9]) == 5);
420 	assert(intrsctn_ab.hasRange([1, 5, 9]) == 3);
421 	assert(intrsctn_ac.hasRange([3, 7]) == 2);
422 	assert(cmplmnt_ab.hasRange([3, 7]) == 2);
423 	assert(cmplmnt_ac.hasRange([1, 5, 9]) == 3);
424 	assert(diff_ab.hasRange([3, 7]) == 2);
425 	assert(diff_ac.hasRange([1, 5, 9]) == 3);
426 	assert(diff_bc.hasRange([1, 3, 5, 7, 9]) == 5);
427 	assert(5 in diff_bc);
428 }