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 }