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 dalicious.range; 12 import std.algorithm; 13 import std.conv : to; 14 import std.functional : binaryFun, unaryFun; 15 import std.meta; 16 import std.traits; 17 import std.typecons; 18 import std.range; 19 import std.range.primitives; 20 21 22 /** 23 Slices an input array into slices of equivalent adjacent elements. 24 In other languages this is often called `partitionBy`, `groupBy` 25 or `sliceWhen`. 26 27 Equivalence is defined by the predicate `pred`, which can be binary, 28 which is passed to `std.functional.binaryFun`. Two range elements 29 `a` and `b` are considered equivalent if `pred(a,b)` is true. 30 31 This predicate must be an equivalence relation, that is, it must be 32 reflexive (`pred(x,x)` is always true), symmetric 33 (`pred(x,y) == pred(y,x)`), and transitive (`pred(x,y) && pred(y,z)` 34 implies `pred(x,z)`). If this is not the case, the range returned by 35 sliceBy may assert at runtime or behave erratically. 36 37 Params: 38 pred = Predicate for determining equivalence. 39 array = An array to be sliced. 40 41 Returns: With a binary predicate, a range of slices is returned in which 42 all elements in a given slice are equivalent under the given predicate. 43 44 Notes: 45 46 Equivalent elements separated by an intervening non-equivalent element will 47 appear in separate subranges; this function only considers adjacent 48 equivalence. Elements in the subranges will always appear in the same order 49 they appear in the original range. 50 */ 51 auto sliceBy(alias pred, Array)(Array array) pure nothrow 52 if (isDynamicArray!Array) 53 { 54 return SliceByImpl!(pred, Array)(array); 55 } 56 57 /// 58 unittest 59 { 60 import std.algorithm.comparison : equal; 61 62 // Grouping by particular attribute of each element: 63 auto data = [ 64 [1, 1], 65 [1, 2], 66 [2, 2], 67 [2, 3] 68 ]; 69 70 auto r1 = data.sliceBy!((a,b) => a[0] == b[0]); 71 assert(r1.equal([ 72 data[0 .. 2], 73 data[2 .. 4] 74 ])); 75 76 auto r2 = data.sliceBy!((a,b) => a[1] == b[1]); 77 assert(r2.equal([ 78 data[0 .. 1], 79 data[1 .. 3], 80 data[3 .. 4], 81 ])); 82 } 83 84 private struct SliceByImpl(alias pred, Array) 85 if (isDynamicArray!Array) 86 { 87 private alias equivalent = binaryFun!pred; 88 89 private Array _array; 90 private size_t sliceStart; 91 private size_t sliceEnd; 92 93 this(Array array) 94 { 95 this._array = array; 96 97 if (!empty) 98 { 99 popFront(); 100 } 101 } 102 103 void popFront() 104 { 105 assert(!empty, "Attempting to popFront an empty SliceByImpl"); 106 107 sliceStart = sliceEnd++; 108 109 if (empty) 110 { 111 return; 112 } 113 114 auto refElement = _array[sliceStart]; 115 while (sliceEnd < _array.length && equivalent(refElement, _array[sliceEnd])) 116 { 117 ++sliceEnd; 118 } 119 } 120 121 @property bool empty() const pure nothrow 122 { 123 return sliceStart >= _array.length; 124 } 125 126 @property auto front() 127 { 128 assert(!empty, "Attempting to fetch the front of an empty SliceByImpl"); 129 130 return _array[sliceStart .. sliceEnd]; 131 } 132 133 @property SliceByImpl!(pred, Array) save() const pure nothrow 134 { 135 return cast(typeof(return)) this; 136 } 137 } 138 139 140 /** 141 Create chains of items linked by `areChainable`. This similar to `sliceBy` 142 but `areChainable` does not have to be an equivalence relation. 143 144 Params: 145 areChainable = Predicate for determining if two adjacent elements 146 should be chained. 147 array = An array to be sliced. 148 149 Returns: With a binary predicate, a range of slices is returned in which 150 predicate holds for every pair of adjacent elements in a given slice. 151 */ 152 auto chainBy(alias pred, Array)(Array array) pure nothrow 153 if (isDynamicArray!Array) 154 { 155 return ChainByImpl!(pred, Array)(array); 156 } 157 158 /// 159 unittest 160 { 161 import dalicious.math : absdiff; 162 import std.algorithm.comparison : equal; 163 164 // Chain elements that are not too far apart 165 auto data = [1, 2, 3, 2, 1, 8, 5, 6, 7]; 166 167 auto r1 = data.chainBy!((a, b) => absdiff(a, b) <= 1); 168 assert(r1.equal([ 169 data[0 .. 5], 170 data[5 .. 6], 171 data[6 .. 9], 172 ])); 173 } 174 175 private struct ChainByImpl(alias _areChainable, Array) 176 if (isDynamicArray!Array) 177 { 178 private alias areChainable = binaryFun!_areChainable; 179 180 private Array _array; 181 private size_t sliceStart; 182 private size_t sliceEnd; 183 184 this(Array array) 185 { 186 this._array = array; 187 188 if (!empty) 189 popFront(); 190 } 191 192 void popFront() 193 { 194 assert(!empty, "Attempting to popFront an empty ChainByImpl"); 195 196 sliceStart = sliceEnd++; 197 198 if (empty) 199 return; 200 201 while (sliceEnd < _array.length && areChainable(_array[sliceEnd - 1], _array[sliceEnd])) 202 ++sliceEnd; 203 } 204 205 @property bool empty() const pure nothrow 206 { 207 return sliceStart >= _array.length; 208 } 209 210 @property auto front() 211 { 212 assert(!empty, "Attempting to fetch the front of an empty ChainByImpl"); 213 214 return _array[sliceStart .. sliceEnd]; 215 } 216 217 @property ChainByImpl!(_areChainable, Array) save() const pure nothrow 218 { 219 return cast(typeof(return)) this; 220 } 221 } 222 223 /// Return the prefix of `haystack` where `pred` is not satisfied. 224 Array sliceUntil(alias pred = "a == b", Array, Needle)( 225 Array haystack, 226 Needle needle, 227 OpenRight openRight = Yes.openRight, 228 ) 229 if (isDynamicArray!Array) 230 { 231 alias predFun = binaryFun!pred; 232 233 foreach (i, ref e; haystack) 234 if (predFun(e, needle)) 235 return haystack[0 .. (openRight ? i : i + 1)]; 236 237 return haystack[0 .. $]; 238 } 239 240 /// ditto 241 Array sliceUntil(alias pred = "a", Array)( 242 Array haystack, 243 OpenRight openRight = Yes.openRight, 244 ) 245 if (isDynamicArray!Array) 246 { 247 alias predFun = unaryFun!pred; 248 249 foreach (i, ref e; haystack) 250 if (predFun(e)) 251 return haystack[0 .. (openRight ? i : i + 1)]; 252 253 return haystack[0 .. $]; 254 } 255 256 /// 257 unittest 258 { 259 import std.typecons : No; 260 261 int[] a = [ 1, 2, 4, 7, 7, 2, 4, 7, 3, 5]; 262 assert(a.sliceUntil(7) == [1, 2, 4]); 263 assert(a.sliceUntil!"a == 7" == [1, 2, 4]); 264 assert(a.sliceUntil(7, No.openRight) == [1, 2, 4, 7]); 265 } 266 267 /// Returns array filtered in-place. 268 auto ref Array filterInPlace(alias pred = "a", Array)(auto ref Array array) if (isDynamicArray!Array) 269 { 270 import std.algorithm : filter; 271 272 auto bufferRest = array.filter!pred.copy(array); 273 274 array.length -= bufferRest.length; 275 276 return array; 277 } 278 279 /// 280 unittest 281 { 282 alias isEven = n => n % 2 == 0; 283 auto arr = [1, 2, 2, 2, 3, 3, 4]; 284 285 assert(filterInPlace!isEven(arr) == [2, 2, 2, 4]); 286 // The input array gets modified. 287 assert(arr == [2, 2, 2, 4]); 288 289 // Can be called with non-lvalues 290 assert(filterInPlace!isEven([1, 2, 2, 2, 3, 3, 4]) == [2, 2, 2, 4]); 291 } 292 293 /// Returns array `uniq`ified in-place. 294 auto ref Array uniqInPlace(alias pred = "a == b", Array)(auto ref Array array) if (isDynamicArray!Array) 295 { 296 auto bufferRest = array.uniq.copy(array); 297 298 array.length -= bufferRest.length; 299 300 return array; 301 } 302 303 /// 304 unittest 305 { 306 auto arr = [1, 2, 2, 2, 3, 3, 4]; 307 308 assert(uniqInPlace(arr) == [1, 2, 3, 4]); 309 // The input array gets modified. 310 assert(arr == [1, 2, 3, 4]); 311 312 // Can be called with non-lvalues 313 assert(uniqInPlace([1, 2, 2, 2, 3, 3, 4]) == [1, 2, 3, 4]); 314 } 315 316 /// Replaces the first occurrence of `needle` by `replacement` in `array` if 317 /// present. Modifies array. 318 Array replaceInPlace(alias pred = "a == b", Array, E)(auto ref Array array, E needle, E replacement) 319 if (isDynamicArray!Array) 320 { 321 auto i = array.countUntil!pred(needle); 322 323 if (i < 0) 324 return array; 325 326 array[i] = replacement; 327 328 return array; 329 } 330 331 /// 332 unittest 333 { 334 auto arr = [1, 2, 3, 4, 2, 3]; 335 336 assert(arr.replaceInPlace(2, 7) == [1, 7, 3, 4, 2, 3]); 337 // The input array gets modified. 338 assert(arr == [1, 7, 3, 4, 2, 3]); 339 // Replaces only the first occurrence 340 assert(arr.replaceInPlace(2, 7) == [1, 7, 3, 4, 7, 3]); 341 342 // Can be called with non-lvalues 343 assert([1, 2, 3].replaceInPlace(2, 7) == [1, 7, 3]); 344 } 345 346 /// Get the first element in range assuming it to be non-empty. 347 ElementType!Range first(Range)(Range range) if (isInputRange!Range) 348 { 349 assert(!range.empty, "must not call first on an empty range"); 350 351 return range.front; 352 } 353 354 /// 355 unittest 356 { 357 assert(first([1, 2, 3]) == 1); 358 assert(first("abcd") == 'a'); 359 } 360 361 /// Get the last element in range assuming it to be non-empty. 362 ElementType!Range last(Range)(Range range) if (isInputRange!Range) 363 { 364 assert(!range.empty, "must not call last on an empty range"); 365 366 static if (isBidirectionalRange!Range) 367 { 368 return range.back; 369 } 370 else static if (hasLength!Range) 371 { 372 foreach (i; 0 .. range.length - 1) 373 range.popFront(); 374 375 return range.front; 376 } 377 else static if (isForwardRange!Range) 378 { 379 auto checkpoint = range; 380 381 while (!range.empty) 382 { 383 checkpoint = range.save; 384 range.popFront(); 385 } 386 387 return checkpoint.front; 388 } 389 else 390 { 391 typeof(return) lastElement; 392 393 while (!range.empty) 394 { 395 lastElement = range.front; 396 range.popFront(); 397 } 398 399 return lastElement; 400 } 401 402 } 403 404 /// 405 unittest 406 { 407 import std.algorithm : filter; 408 import std.range : take, takeExactly; 409 410 struct PowersOfTwo(bool shouldSave) 411 { 412 size_t i = 1; 413 414 void popFront() pure nothrow 415 { 416 i *= 2; 417 } 418 419 @property size_t front() const pure nothrow 420 { 421 return i + 0; 422 } 423 424 @property bool empty() const pure nothrow 425 { 426 return false; 427 } 428 429 static if (shouldSave) 430 { 431 @property PowersOfTwo save() const pure nothrow 432 { 433 return cast(typeof(return)) this; 434 } 435 } 436 } 437 438 assert(last([1, 2, 3]) == 3); 439 assert(last(PowersOfTwo!true(1).takeExactly(5)) == 16); 440 assert(last(PowersOfTwo!true(1).take(5)) == 16); 441 assert(last(PowersOfTwo!false(1).take(5)) == 16); 442 } 443 444 /** 445 Find an optimal solution using backtracking. 446 */ 447 T[] backtracking(alias isFeasible, alias score, T)( 448 T[] candidates, 449 T[] solution = [], 450 ) 451 { 452 auto optimalSolution = solution; 453 auto optimalScore = score(optimalSolution); 454 455 foreach (i, candidate; candidates) 456 { 457 if (isFeasible(cast(const(T[])) solution ~ candidate)) 458 { 459 auto newSolution = backtracking!(isFeasible, score)( 460 candidates[0 .. i] ~ candidates[i + 1 .. $], 461 solution ~ candidate, 462 ); 463 auto newScore = score(cast(const(T[])) newSolution); 464 465 if (newScore > optimalScore) 466 optimalSolution = newSolution; 467 } 468 } 469 470 return optimalSolution; 471 } 472 473 474 import std.algorithm : map; 475 476 /// Cast elements to `const(char)`. 477 alias charRange = map!"cast(const char) a"; 478 479 480 /// 481 struct Coiterator(alias cmp="a < b", Rs...) 482 if (allSatisfy!(isInputRange, Rs) && Rs.length >= 2 && 483 is(CommonType!(ElementType, Rs))) 484 { 485 alias E = CommonType!(ElementType, Rs); 486 alias lowerThan = binaryFun!cmp; 487 488 private Rs sources; 489 private ptrdiff_t frontIndex; 490 491 492 /// 493 this(Rs sources) 494 { 495 this.sources = sources; 496 this.frontIndex = 0; 497 498 if (!empty) 499 advanceSources(); 500 } 501 502 503 /// 504 @property bool empty() 505 { 506 return frontIndex < 0 || only(sources).any!"a.empty"; 507 } 508 509 510 /// 511 @property auto front() 512 { 513 assert(!empty, "Attempting to fetch the front of an empty " ~ typeof(this).stringof); 514 515 return tupleMap!"a.front"(sources); 516 } 517 518 /// 519 void popFront() 520 { 521 static foreach (i; 0 .. sources.length) 522 popFrontSource(sources[i]); 523 advanceSources(); 524 } 525 526 527 private void advanceSources() 528 { 529 if (frontSourceEmpty) 530 { 531 frontIndex = -1; 532 return; 533 } 534 535 bool allLinedUp; 536 537 while (!allLinedUp) 538 { 539 // assume they are lined up and proof the contrary 540 allLinedUp = true; 541 foreach (i, ref source; sources) 542 { 543 // disregard the current frontIndex 544 while (!source.empty && lowerThan(source.front, frontElement)) 545 popFrontSource(source); 546 547 if (source.empty) 548 { 549 // end of co-iteration 550 frontIndex = -1; 551 552 return; 553 } 554 else if (lowerThan(frontElement, source.front)) 555 { 556 // source advanced beyond the sources[frontIndex] 557 frontIndex = i; 558 allLinedUp = false; 559 } 560 } 561 } 562 } 563 564 565 private void popFrontSource(R)(ref R source) 566 { 567 version (assert) 568 auto lastElement = source.front; 569 570 source.popFront(); 571 572 version (assert) 573 assert( 574 source.empty || lowerThan(lastElement, source.front), 575 "sources must be strictly ascending", 576 ); 577 578 } 579 580 581 private @property auto ref frontElement() 582 { 583 static foreach (i; 0 .. sources.length) 584 if (i == frontIndex) 585 return sources[i].front; 586 assert(0, "out of bounds"); 587 } 588 589 590 private @property bool frontSourceEmpty() 591 { 592 static foreach (i; 0 .. sources.length) 593 if (i == frontIndex) 594 return sources[i].empty; 595 assert(0, "out of bounds"); 596 } 597 598 599 /// 600 static if (allSatisfy!(isForwardRange, Rs)) 601 @property auto save() 602 { 603 return typeof(this)(tupleMap!"a.save"(sources).expand); 604 } 605 } 606 607 608 /// 609 auto coiterate(alias cmp="a < b", Rs...)(Rs sources) 610 if (allSatisfy!(isInputRange, Rs) && Rs.length >= 2) 611 { 612 return Coiterator!(cmp, Rs)(sources); 613 } 614 615 /// 616 unittest 617 { 618 assert(equal( 619 coiterate( 620 [1, 2, 3, 4, 5], 621 [ 2, 3, 4, 5, 6], 622 [1, 3, 5], 623 [1, 2, 3, 4, 5, 6], 624 ), 625 [ 626 tuple(3, 3, 3, 3), 627 tuple(5, 5, 5, 5), 628 ], 629 )); 630 }