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;
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     Order `a` and `b` lexicographically by applying each `fun` to them. For
25     unary functions compares `fun(a) < fun(b)`.
26 */
27 bool orderLexicographically(T, fun...)(T a, T b)
28 {
29     static foreach (i, getFieldValue; fun)
30     {
31         {
32             auto aValue = unaryFun!getFieldValue(a);
33             auto bValue = unaryFun!getFieldValue(b);
34 
35             if (aValue != bValue)
36             {
37                 return aValue < bValue;
38             }
39         }
40     }
41 
42     return false;
43 }
44 
45 /**
46     Compare `a` and `b` lexicographically by applying each `fun` to them. For
47     unary functions compares `fun(a) < fun(b)`.
48 */
49 int cmpLexicographically(T, fun...)(T a, T b)
50 {
51     static foreach (i, getFieldValue; fun)
52     {
53         {
54             auto aValue = unaryFun!getFieldValue(a);
55             auto bValue = unaryFun!getFieldValue(b);
56 
57             if (aValue < bValue)
58             {
59                 return -1;
60             }
61             else if (aValue > bValue)
62             {
63                 return 1;
64             }
65         }
66     }
67 
68     return 0;
69 }
70 
71 /**
72     Slices an input array into slices of equivalent adjacent elements.
73     In other languages this is often called `partitionBy`, `groupBy`
74     or `sliceWhen`.
75 
76     Equivalence is defined by the predicate `pred`, which can be binary,
77     which is passed to `std.functional.binaryFun`. Two range elements
78     `a` and `b` are considered equivalent if `pred(a,b)` is true.
79 
80     This predicate must be an equivalence relation, that is, it must be
81     reflexive (`pred(x,x)` is always true), symmetric
82     (`pred(x,y) == pred(y,x)`), and transitive (`pred(x,y) && pred(y,z)`
83     implies `pred(x,z)`). If this is not the case, the range returned by
84     sliceBy may assert at runtime or behave erratically.
85 
86     Params:
87      pred = Predicate for determining equivalence.
88      r = An array to be sliced.
89 
90     Returns: With a binary predicate, a range of slices is returned in which
91     all elements in a given slice are equivalent under the given predicate.
92 
93     Notes:
94 
95     Equivalent elements separated by an intervening non-equivalent element will
96     appear in separate subranges; this function only considers adjacent
97     equivalence. Elements in the subranges will always appear in the same order
98     they appear in the original range.
99 */
100 auto sliceBy(alias pred, Array)(Array array) pure nothrow
101         if (isDynamicArray!Array)
102 {
103     return SliceByImpl!(pred, Array)(array);
104 }
105 
106 ///
107 unittest
108 {
109     import std.algorithm.comparison : equal;
110 
111     // Grouping by particular attribute of each element:
112     auto data = [
113         [1, 1],
114         [1, 2],
115         [2, 2],
116         [2, 3]
117     ];
118 
119     auto r1 = data.sliceBy!((a,b) => a[0] == b[0]);
120     assert(r1.equal([
121         data[0 .. 2],
122         data[2 .. 4]
123     ]));
124 
125     auto r2 = data.sliceBy!((a,b) => a[1] == b[1]);
126     assert(r2.equal([
127         data[0 .. 1],
128         data[1 .. 3],
129         data[3 .. 4],
130     ]));
131 }
132 
133 private struct SliceByImpl(alias pred, Array)
134         if (isDynamicArray!Array)
135 {
136     private alias equivalent = binaryFun!pred;
137 
138     private Array array;
139     private size_t sliceStart;
140     private size_t sliceEnd;
141 
142     this(Array array)
143     {
144         this.array = array;
145 
146         if (!empty)
147         {
148             popFront();
149         }
150     }
151 
152     void popFront()
153     {
154         assert(!empty, "Attempting to popFront an empty SliceByImpl");
155 
156         sliceStart = sliceEnd++;
157 
158         if (empty)
159         {
160             return;
161         }
162 
163         auto refElement = array[sliceStart];
164         while (sliceEnd < array.length && equivalent(refElement, array[sliceEnd]))
165         {
166             ++sliceEnd;
167         }
168     }
169 
170     @property bool empty() const pure nothrow
171     {
172         return sliceStart >= array.length;
173     }
174 
175     @property auto front()
176     {
177         assert(!empty, "Attempting to fetch the front of an empty SliceByImpl");
178 
179         return array[sliceStart .. sliceEnd];
180     }
181 }
182 
183 /// Return the prefix of `haystack` where `pred` is not satisfied.
184 Array sliceUntil(alias pred = "a == b", Array, Needle)(
185     Array haystack,
186     Needle needle,
187     OpenRight openRight = Yes.openRight,
188 )
189         if (isDynamicArray!Array)
190 {
191     alias predFun = binaryFun!pred;
192 
193     foreach (i, ref e; haystack)
194         if (predFun(e, needle))
195             return haystack[0 .. (openRight ? i : i + 1)];
196 
197     return haystack[0 .. $];
198 }
199 
200 /// ditto
201 Array sliceUntil(alias pred = "a", Array)(
202     Array haystack,
203     OpenRight openRight = Yes.openRight,
204 )
205         if (isDynamicArray!Array)
206 {
207     alias predFun = unaryFun!pred;
208 
209     foreach (i, ref e; haystack)
210         if (predFun(e))
211             return haystack[0 .. (openRight ? i : i + 1)];
212 
213     return haystack[0 .. $];
214 }
215 
216 ///
217 unittest
218 {
219     import std.typecons : No;
220 
221     int[] a = [ 1, 2, 4, 7, 7, 2, 4, 7, 3, 5];
222     assert(a.sliceUntil(7) == [1, 2, 4]);
223     assert(a.sliceUntil!"a == 7" == [1, 2, 4]);
224     assert(a.sliceUntil(7, No.openRight) == [1, 2, 4, 7]);
225 }
226 
227 /// Returns array filtered in-place.
228 auto ref Array filterInPlace(alias pred = "a", Array)(auto ref Array array) if (isDynamicArray!Array)
229 {
230     import std.algorithm : filter;
231 
232     auto bufferRest = array.filter!pred.copy(array);
233 
234     array.length -= bufferRest.length;
235 
236     return array;
237 }
238 
239 ///
240 unittest
241 {
242     alias isEven = n => n % 2 == 0;
243     auto arr = [1, 2, 2, 2, 3, 3, 4];
244 
245     assert(filterInPlace!isEven(arr) == [2, 2, 2, 4]);
246     // The input array gets modified.
247     assert(arr == [2, 2, 2, 4]);
248 
249     // Can be called with non-lvalues
250     assert(filterInPlace!isEven([1, 2, 2, 2, 3, 3, 4]) == [2, 2, 2, 4]);
251 }
252 
253 /// Returns array `uniq`ified in-place.
254 auto ref Array uniqInPlace(alias pred = "a == b", Array)(auto ref Array array) if (isDynamicArray!Array)
255 {
256     auto bufferRest = array.uniq.copy(array);
257 
258     array.length -= bufferRest.length;
259 
260     return array;
261 }
262 
263 ///
264 unittest
265 {
266     auto arr = [1, 2, 2, 2, 3, 3, 4];
267 
268     assert(uniqInPlace(arr) == [1, 2, 3, 4]);
269     // The input array gets modified.
270     assert(arr == [1, 2, 3, 4]);
271 
272     // Can be called with non-lvalues
273     assert(uniqInPlace([1, 2, 2, 2, 3, 3, 4]) == [1, 2, 3, 4]);
274 }
275 
276 /// Replaces the first occurrence of `needle` by `replacement` in `array` if
277 /// present. Modifies array.
278 Array replaceInPlace(alias pred = "a == b", Array, E)(auto ref Array array, E needle, E replacement)
279         if (isDynamicArray!Array)
280 {
281     auto i = array.countUntil!pred(needle);
282 
283     if (i < 0)
284         return array;
285 
286     array[i] = replacement;
287 
288     return array;
289 }
290 
291 ///
292 unittest
293 {
294     auto arr = [1, 2, 3, 4, 2, 3];
295 
296     assert(arr.replaceInPlace(2, 7) == [1, 7, 3, 4, 2, 3]);
297     // The input array gets modified.
298     assert(arr == [1, 7, 3, 4, 2, 3]);
299     // Replaces only the first occurrence
300     assert(arr.replaceInPlace(2, 7) == [1, 7, 3, 4, 7, 3]);
301 
302     // Can be called with non-lvalues
303     assert([1, 2, 3].replaceInPlace(2, 7) == [1, 7, 3]);
304 }
305 
306 /// Get the first element in range assuming it to be non-empty.
307 ElementType!Range first(Range)(Range range) if (isInputRange!Range)
308 {
309     assert(!range.empty, "must not call first on an empty range");
310 
311     return range.front;
312 }
313 
314 ///
315 unittest
316 {
317     assert(first([1, 2, 3]) == 1);
318     assert(first("abcd") == 'a');
319 }
320 
321 /// Get the last element in range assuming it to be non-empty.
322 ElementType!Range last(Range)(Range range) if (isInputRange!Range)
323 {
324     import std.stdio;
325     assert(!range.empty, "must not call last on an empty range");
326 
327     static if (isBidirectionalRange!Range)
328     {
329         return range.back;
330     }
331     else static if (hasLength!Range)
332     {
333         foreach (i; 0 .. range.length - 1)
334             range.popFront();
335 
336         return range.front;
337     }
338     else static if (isForwardRange!Range)
339     {
340         auto checkpoint = range;
341 
342         while (!range.empty)
343         {
344             checkpoint = range.save;
345             range.popFront();
346         }
347 
348         return checkpoint.front;
349     }
350     else
351     {
352         typeof(return) lastElement;
353 
354         while (!range.empty)
355         {
356             lastElement = range.front;
357             range.popFront();
358         }
359 
360         return lastElement;
361     }
362 
363 }
364 
365 ///
366 unittest
367 {
368     import std.algorithm : filter;
369     import std.range : take, takeExactly;
370 
371     struct PowersOfTwo(bool shouldSave)
372     {
373         size_t i = 1;
374 
375         void popFront() pure nothrow
376         {
377             i *= 2;
378         }
379 
380         @property size_t front() const pure nothrow
381         {
382             return i + 0;
383         }
384 
385         @property bool empty() const pure nothrow
386         {
387             return false;
388         }
389 
390         static if (shouldSave)
391         {
392             @property PowersOfTwo save() const pure nothrow
393             {
394                 return cast(typeof(return)) this;
395             }
396         }
397     }
398 
399     assert(last([1, 2, 3]) == 3);
400     assert(last(PowersOfTwo!true(1).takeExactly(5)) == 16);
401     assert(last(PowersOfTwo!true(1).take(5)) == 16);
402     assert(last(PowersOfTwo!false(1).take(5)) == 16);
403 }
404 
405 /// Returns one of a collection of expressions based on the value of the
406 /// switch expression.
407 template staticPredSwitch(T...)
408 {
409     auto staticPredSwitch(E)(E switchExpression) pure nothrow
410     {
411         static assert (T.length > 0, "missing choices");
412         static assert (T.length % 2 == 1, "missing default clause");
413 
414         static foreach (i; 0 .. T.length - 1)
415         {
416             static if (i % 2 == 0)
417             {
418                 if (switchExpression == T[i])
419                     return T[i + 1];
420             }
421         }
422 
423         return T[$ - 1];
424     }
425 }
426 
427 ///
428 unittest
429 {
430     alias numberName = staticPredSwitch!(
431         1, "one",
432         2, "two",
433         3, "three",
434         "many",
435     );
436 
437     static assert("one" == numberName(1));
438     static assert("two" == numberName(2));
439     static assert("three" == numberName(3));
440     static assert("many" == numberName(4));
441 }
442 
443 /**
444     Find an optimal solution using backtracking.
445 */
446 T[] backtracking(alias isFeasible, alias score, T)(
447     T[] candidates,
448     T[] solution = [],
449 )
450 {
451     auto optimalSolution = solution;
452     auto optimalScore = score(optimalSolution);
453 
454     foreach (i, candidate; candidates)
455     {
456         if (isFeasible(cast(const(T[])) solution ~ candidate))
457         {
458             auto newSolution = backtracking!(isFeasible, score)(
459                 candidates[0 .. i] ~ candidates[i + 1 .. $],
460                 solution ~ candidate,
461             );
462             auto newScore = score(cast(const(T[])) newSolution);
463 
464             if (newScore > optimalScore)
465                 optimalSolution = newSolution;
466         }
467     }
468 
469     return optimalSolution;
470 }
471