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 }