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