1 /** 2 Some additional alogorithm functions. 3 4 Copyright: © 2019 Arne Ludwig <arne.ludwig@posteo.de> 5 License: Subject to the terms of the MIT license, as written in the 6 included LICENSE file. 7 Authors: Arne Ludwig <arne.ludwig@posteo.de> 8 */ 9 module dalicious.algorithm.iteration; 10 11 import std.algorithm : 12 copy, 13 countUntil, 14 min, 15 OpenRight, 16 uniq; 17 import std.conv : to; 18 import std.functional : binaryFun, unaryFun; 19 import std.traits : isDynamicArray; 20 import std.typecons : Yes; 21 import std.range.primitives; 22 23 24 /** 25 Slices an input array into slices of equivalent adjacent elements. 26 In other languages this is often called `partitionBy`, `groupBy` 27 or `sliceWhen`. 28 29 Equivalence is defined by the predicate `pred`, which can be binary, 30 which is passed to `std.functional.binaryFun`. Two range elements 31 `a` and `b` are considered equivalent if `pred(a,b)` is true. 32 33 This predicate must be an equivalence relation, that is, it must be 34 reflexive (`pred(x,x)` is always true), symmetric 35 (`pred(x,y) == pred(y,x)`), and transitive (`pred(x,y) && pred(y,z)` 36 implies `pred(x,z)`). If this is not the case, the range returned by 37 sliceBy may assert at runtime or behave erratically. 38 39 Params: 40 pred = Predicate for determining equivalence. 41 array = An array to be sliced. 42 43 Returns: With a binary predicate, a range of slices is returned in which 44 all elements in a given slice are equivalent under the given predicate. 45 46 Notes: 47 48 Equivalent elements separated by an intervening non-equivalent element will 49 appear in separate subranges; this function only considers adjacent 50 equivalence. Elements in the subranges will always appear in the same order 51 they appear in the original range. 52 */ 53 auto sliceBy(alias pred, Array)(Array array) pure nothrow 54 if (isDynamicArray!Array) 55 { 56 return SliceByImpl!(pred, Array)(array); 57 } 58 59 /// 60 unittest 61 { 62 import std.algorithm.comparison : equal; 63 64 // Grouping by particular attribute of each element: 65 auto data = [ 66 [1, 1], 67 [1, 2], 68 [2, 2], 69 [2, 3] 70 ]; 71 72 auto r1 = data.sliceBy!((a,b) => a[0] == b[0]); 73 assert(r1.equal([ 74 data[0 .. 2], 75 data[2 .. 4] 76 ])); 77 78 auto r2 = data.sliceBy!((a,b) => a[1] == b[1]); 79 assert(r2.equal([ 80 data[0 .. 1], 81 data[1 .. 3], 82 data[3 .. 4], 83 ])); 84 } 85 86 private struct SliceByImpl(alias pred, Array) 87 if (isDynamicArray!Array) 88 { 89 private alias equivalent = binaryFun!pred; 90 91 private Array _array; 92 private size_t sliceStart; 93 private size_t sliceEnd; 94 95 this(Array array) 96 { 97 this._array = array; 98 99 if (!empty) 100 { 101 popFront(); 102 } 103 } 104 105 void popFront() 106 { 107 assert(!empty, "Attempting to popFront an empty SliceByImpl"); 108 109 sliceStart = sliceEnd++; 110 111 if (empty) 112 { 113 return; 114 } 115 116 auto refElement = _array[sliceStart]; 117 while (sliceEnd < _array.length && equivalent(refElement, _array[sliceEnd])) 118 { 119 ++sliceEnd; 120 } 121 } 122 123 @property bool empty() const pure nothrow 124 { 125 return sliceStart >= _array.length; 126 } 127 128 @property auto front() 129 { 130 assert(!empty, "Attempting to fetch the front of an empty SliceByImpl"); 131 132 return _array[sliceStart .. sliceEnd]; 133 } 134 135 @property SliceByImpl!(pred, Array) save() const pure nothrow 136 { 137 return cast(typeof(return)) this; 138 } 139 } 140 141 /// Return the prefix of `haystack` where `pred` is not satisfied. 142 Array sliceUntil(alias pred = "a == b", Array, Needle)( 143 Array haystack, 144 Needle needle, 145 OpenRight openRight = Yes.openRight, 146 ) 147 if (isDynamicArray!Array) 148 { 149 alias predFun = binaryFun!pred; 150 151 foreach (i, ref e; haystack) 152 if (predFun(e, needle)) 153 return haystack[0 .. (openRight ? i : i + 1)]; 154 155 return haystack[0 .. $]; 156 } 157 158 /// ditto 159 Array sliceUntil(alias pred = "a", Array)( 160 Array haystack, 161 OpenRight openRight = Yes.openRight, 162 ) 163 if (isDynamicArray!Array) 164 { 165 alias predFun = unaryFun!pred; 166 167 foreach (i, ref e; haystack) 168 if (predFun(e)) 169 return haystack[0 .. (openRight ? i : i + 1)]; 170 171 return haystack[0 .. $]; 172 } 173 174 /// 175 unittest 176 { 177 import std.typecons : No; 178 179 int[] a = [ 1, 2, 4, 7, 7, 2, 4, 7, 3, 5]; 180 assert(a.sliceUntil(7) == [1, 2, 4]); 181 assert(a.sliceUntil!"a == 7" == [1, 2, 4]); 182 assert(a.sliceUntil(7, No.openRight) == [1, 2, 4, 7]); 183 } 184 185 /// Returns array filtered in-place. 186 auto ref Array filterInPlace(alias pred = "a", Array)(auto ref Array array) if (isDynamicArray!Array) 187 { 188 import std.algorithm : filter; 189 190 auto bufferRest = array.filter!pred.copy(array); 191 192 array.length -= bufferRest.length; 193 194 return array; 195 } 196 197 /// 198 unittest 199 { 200 alias isEven = n => n % 2 == 0; 201 auto arr = [1, 2, 2, 2, 3, 3, 4]; 202 203 assert(filterInPlace!isEven(arr) == [2, 2, 2, 4]); 204 // The input array gets modified. 205 assert(arr == [2, 2, 2, 4]); 206 207 // Can be called with non-lvalues 208 assert(filterInPlace!isEven([1, 2, 2, 2, 3, 3, 4]) == [2, 2, 2, 4]); 209 } 210 211 /// Returns array `uniq`ified in-place. 212 auto ref Array uniqInPlace(alias pred = "a == b", Array)(auto ref Array array) if (isDynamicArray!Array) 213 { 214 auto bufferRest = array.uniq.copy(array); 215 216 array.length -= bufferRest.length; 217 218 return array; 219 } 220 221 /// 222 unittest 223 { 224 auto arr = [1, 2, 2, 2, 3, 3, 4]; 225 226 assert(uniqInPlace(arr) == [1, 2, 3, 4]); 227 // The input array gets modified. 228 assert(arr == [1, 2, 3, 4]); 229 230 // Can be called with non-lvalues 231 assert(uniqInPlace([1, 2, 2, 2, 3, 3, 4]) == [1, 2, 3, 4]); 232 } 233 234 /// Replaces the first occurrence of `needle` by `replacement` in `array` if 235 /// present. Modifies array. 236 Array replaceInPlace(alias pred = "a == b", Array, E)(auto ref Array array, E needle, E replacement) 237 if (isDynamicArray!Array) 238 { 239 auto i = array.countUntil!pred(needle); 240 241 if (i < 0) 242 return array; 243 244 array[i] = replacement; 245 246 return array; 247 } 248 249 /// 250 unittest 251 { 252 auto arr = [1, 2, 3, 4, 2, 3]; 253 254 assert(arr.replaceInPlace(2, 7) == [1, 7, 3, 4, 2, 3]); 255 // The input array gets modified. 256 assert(arr == [1, 7, 3, 4, 2, 3]); 257 // Replaces only the first occurrence 258 assert(arr.replaceInPlace(2, 7) == [1, 7, 3, 4, 7, 3]); 259 260 // Can be called with non-lvalues 261 assert([1, 2, 3].replaceInPlace(2, 7) == [1, 7, 3]); 262 } 263 264 /// Get the first element in range assuming it to be non-empty. 265 ElementType!Range first(Range)(Range range) if (isInputRange!Range) 266 { 267 assert(!range.empty, "must not call first on an empty range"); 268 269 return range.front; 270 } 271 272 /// 273 unittest 274 { 275 assert(first([1, 2, 3]) == 1); 276 assert(first("abcd") == 'a'); 277 } 278 279 /// Get the last element in range assuming it to be non-empty. 280 ElementType!Range last(Range)(Range range) if (isInputRange!Range) 281 { 282 assert(!range.empty, "must not call last on an empty range"); 283 284 static if (isBidirectionalRange!Range) 285 { 286 return range.back; 287 } 288 else static if (hasLength!Range) 289 { 290 foreach (i; 0 .. range.length - 1) 291 range.popFront(); 292 293 return range.front; 294 } 295 else static if (isForwardRange!Range) 296 { 297 auto checkpoint = range; 298 299 while (!range.empty) 300 { 301 checkpoint = range.save; 302 range.popFront(); 303 } 304 305 return checkpoint.front; 306 } 307 else 308 { 309 typeof(return) lastElement; 310 311 while (!range.empty) 312 { 313 lastElement = range.front; 314 range.popFront(); 315 } 316 317 return lastElement; 318 } 319 320 } 321 322 /// 323 unittest 324 { 325 import std.algorithm : filter; 326 import std.range : take, takeExactly; 327 328 struct PowersOfTwo(bool shouldSave) 329 { 330 size_t i = 1; 331 332 void popFront() pure nothrow 333 { 334 i *= 2; 335 } 336 337 @property size_t front() const pure nothrow 338 { 339 return i + 0; 340 } 341 342 @property bool empty() const pure nothrow 343 { 344 return false; 345 } 346 347 static if (shouldSave) 348 { 349 @property PowersOfTwo save() const pure nothrow 350 { 351 return cast(typeof(return)) this; 352 } 353 } 354 } 355 356 assert(last([1, 2, 3]) == 3); 357 assert(last(PowersOfTwo!true(1).takeExactly(5)) == 16); 358 assert(last(PowersOfTwo!true(1).take(5)) == 16); 359 assert(last(PowersOfTwo!false(1).take(5)) == 16); 360 } 361 362 /** 363 Find an optimal solution using backtracking. 364 */ 365 T[] backtracking(alias isFeasible, alias score, T)( 366 T[] candidates, 367 T[] solution = [], 368 ) 369 { 370 auto optimalSolution = solution; 371 auto optimalScore = score(optimalSolution); 372 373 foreach (i, candidate; candidates) 374 { 375 if (isFeasible(cast(const(T[])) solution ~ candidate)) 376 { 377 auto newSolution = backtracking!(isFeasible, score)( 378 candidates[0 .. i] ~ candidates[i + 1 .. $], 379 solution ~ candidate, 380 ); 381 auto newScore = score(cast(const(T[])) newSolution); 382 383 if (newScore > optimalScore) 384 optimalSolution = newSolution; 385 } 386 } 387 388 return optimalSolution; 389 } 390 391 392 import std.algorithm : map; 393 394 /// Cast elements to `const(char)`. 395 alias charRange = map!"cast(const char) a";