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.comparison;
10 
11 import std.algorithm :
12     copy,
13     countUntil,
14     min,
15     OpenRight,
16     swapAt,
17     uniq;
18 import std.conv : to;
19 import std.functional : binaryFun, unaryFun;
20 import std.traits :
21     CommonType,
22     isCallable,
23     isDynamicArray,
24     ReturnType,
25     rvalueOf;
26 import std.typecons : Flag, No, Yes;
27 import std.range : enumerate;
28 import std.range.primitives;
29 
30 
31 /**
32     Order `a` and `b` lexicographically by applying each `fun` to them. For
33     unary functions compares `fun(a) < fun(b)`.
34 */
35 template orderLexicographically(fun...)
36 {
37     bool _orderLexicographically(T)(T a, T b)
38     {
39         static foreach (i, getFieldValue; fun)
40         {{
41             auto aValue = unaryFun!getFieldValue(a);
42             auto bValue = unaryFun!getFieldValue(b);
43 
44             if (aValue != bValue)
45                 return aValue < bValue;
46         }}
47 
48         return false;
49     }
50 
51     static if (is(fun[0]))
52         alias orderLexicographically = _orderLexicographically!(fun[0]);
53     else
54         alias orderLexicographically = _orderLexicographically;
55 }
56 
57 
58 /**
59     Compare `a` and `b` lexicographically by applying each `fun` to them. For
60     unary functions compares `fun(a) < fun(b)`.
61 */
62 int cmpLexicographically(T, fun...)(T a, T b)
63 {
64     static foreach (i, alias getFieldValue; fun)
65     {
66         {
67             auto aValue = unaryFun!getFieldValue(a);
68             auto bValue = unaryFun!getFieldValue(b);
69 
70             if (aValue < bValue)
71             {
72                 return -1;
73             }
74             else if (aValue > bValue)
75             {
76                 return 1;
77             }
78         }
79     }
80 
81     return 0;
82 }
83 
84 
85 /// Returns one of a collection of expressions based on the value of the
86 /// switch expression.
87 template staticPredSwitch(T...)
88 {
89     auto staticPredSwitch(E)(E switchExpression) pure nothrow
90     {
91         static assert (T.length > 0, "missing choices");
92         enum hasDefaultClause = T.length % 2 == 1;
93 
94         static foreach (i; 0 .. T.length - 1)
95         {
96             static if (i % 2 == 0)
97             {
98                 if (switchExpression == T[i])
99                 {
100                     static if (isCallable!(T[i + 1]))
101                         return T[i + 1]();
102                     else
103                         return T[i + 1];
104                 }
105             }
106         }
107 
108         static if (hasDefaultClause)
109         {
110             static if (isCallable!(T[$ - 1]))
111             {
112                 static if (is(ReturnType!(T[$ - 1]) == void))
113                 {
114                     T[$ - 1]();
115                     assert(0);
116                 }
117                 else
118                     return T[$ - 1]();
119             }
120             else
121             {
122                 return T[$ - 1];
123             }
124         }
125         else
126         {
127             assert(0, "none of the clauses matched in " ~ __FUNCTION__);
128         }
129     }
130 }
131 
132 ///
133 unittest
134 {
135     alias numberName = staticPredSwitch!(
136         1, "one",
137         2, "two",
138         3, "three",
139         "many",
140     );
141 
142     static assert("one" == numberName(1));
143     static assert("two" == numberName(2));
144     static assert("three" == numberName(3));
145     static assert("many" == numberName(4));
146 }
147 
148 
149 /**
150     Checks if both ranges are permutations of each other.
151 
152     pred must be an equivalence relation, e.i. for all inputs:
153 
154         // reflexive
155         pred(a, a) == true
156         // symmetric
157         pred(a, b) == pred(b, a)
158         // transitive
159         !(pred(a, b) && pred(b, c)) || pred(a, c)
160 
161     Params:
162         pred = an optional parameter to change how equality is defined
163         r1 = A finite input range
164         r2 = A finite random access range
165         index = An index into r2 such that r2 permuted by index is a prefix of r1.
166     Returns:
167         `true` if all of the elements in `r1` appear the same number of times in `r2`.
168         Otherwise, returns `false`.
169 */
170 bool isPermutation(alias pred = "a == b", Flag!"nearlySorted" nearlySorted = Yes.nearlySorted, R1, R2)(R1 r1, R2 r2)
171     if (
172         isInputRange!R1 && !isInfinite!R1 &&
173         isRandomAccessRange!R2 && !isInfinite!R2 &&
174         is(CommonType!(ElementType!R1, ElementType!R2))
175     )
176 {
177     static if (hasLength!R1)
178         if (r1.length != r2.length)
179             return false;
180 
181     auto index = new size_t[r2.length];
182 
183     return isPermutation!(pred, nearlySorted)(r1, r2, index);
184 }
185 
186 /// ditto
187 bool isPermutation(alias pred = "a == b", Flag!"nearlySorted" nearlySorted = Yes.nearlySorted, R1, R2)(R1 r1, R2 r2, size_t[] index)
188 if (
189     isInputRange!R1 && !isInfinite!R1 &&
190     isRandomAccessRange!R2 && !isInfinite!R2 &&
191     is(CommonType!(ElementType!R1, ElementType!R2))
192 )
193 in (r2.length <= index.length, "index is too small")
194 {
195     static if (hasLength!R1)
196         if (r1.length != r2.length)
197             return false;
198 
199     return isPermutation!(pred, nearlySorted)(r1, r2, index);
200 }
201 
202 /// ditto
203 bool isPermutation(alias pred = "a == b", Flag!"nearlySorted" nearlySorted = Yes.nearlySorted, R1, R2)(R1 r1, R2 r2, ref size_t[] index)
204 if (
205     isInputRange!R1 && !isInfinite!R1 &&
206     isRandomAccessRange!R2 && !isInfinite!R2 &&
207     is(CommonType!(ElementType!R1, ElementType!R2))
208 )
209 in (r2.length <= index.length, "index is too small")
210 {
211     static if (hasLength!R1)
212         if (r1.length != r2.length)
213         {
214             index = [];
215             return false;
216         }
217 
218     alias _pred = binaryFun!pred;
219     const length2 = r2.length;
220     index = index[0 .. length2];
221 
222     foreach (idx, ref permIndex; index)
223         permIndex = idx;
224 
225     // determine longest common prefix
226     size_t prefixLength = longestCommonPrefix!_pred(r1, r2[]);
227 
228     // determine permutation for the rest
229     size_t i = prefixLength;
230     foreach (e1; r1)
231     {
232         auto j = i;
233 
234         while (j < length2 && !_pred(e1, r2[index[j]]))
235             ++j;
236         assert(j == length2 || _pred(e1, r2[index[j]]));
237 
238         if (j == length2)
239         {
240             index = index[0 .. i];
241             return false;
242         }
243         else
244         {
245             static if (nearlySorted)
246             {
247                 if (j > i)
248                 {
249                     auto tmp = index[j];
250                     // shift index[i .. j] by one index
251                     foreach_reverse (k; i .. j)
252                         index[k + 1] = index[k];
253                     index[i] = tmp;
254                 }
255             }
256             else
257             {
258                 index.swapAt(i, j);
259             }
260         }
261 
262         ++i;
263     }
264 
265     return true;
266 }
267 
268 ///
269 unittest
270 {
271     enum r1 = [1, 2, 3, 4, 5, 6];
272     enum r2 = [1, 3, 2, 4, 5, 6];
273 
274     assert(isPermutation(r1, r2));
275 
276     auto index = new size_t[r2.length];
277     assert(isPermutation(r1, r2, index));
278     assert(index == [0, 2, 1, 3, 4, 5]);
279 }
280 
281 unittest
282 {
283     import std.array : array;
284     import std.random;
285     import std.range : iota;
286 
287     debug (exhaustiveUnittests)
288     {
289         enum size_t n = 1024;
290         enum size_t m = 4096;
291     }
292     else
293     {
294         enum size_t n = 64;
295         enum size_t m = 32;
296     }
297     const size_t[] sorted = iota(n).array;
298     size_t[] randomlyShuffled = iota(n).array;
299     auto index = new size_t[n];
300 
301     foreach (_; 0 .. m)
302     {
303         randomShuffle(randomlyShuffled);
304 
305         assert(isPermutation!"a == b"(sorted, randomlyShuffled, index));
306     }
307 }
308 
309 debug (benchmark) unittest
310 {
311     import std.algorithm : equal;
312     import std.array : array;
313     import std.datetime.stopwatch;
314     import std.random;
315     import std.range : iota;
316     import std.stdio : writefln;
317 
318     enum size_t n = 64;
319     const size_t[] sorted = iota(n).array;
320     const size_t[] shortSwap = sorted[0 .. n/2] ~ [sorted[n/2 + 1], sorted[n/2]] ~ sorted[n/2 + 2 .. $];
321     const size_t[] longSwap = sorted[1 .. $] ~ sorted[0 .. 1];
322     const size_t[] randomlyShuffled = iota(n).array.randomShuffle;
323     auto index = new size_t[n];
324     alias eq = (size_t a, size_t b) => a == b;
325     auto res = benchmark!(
326         { cast(void) equal!eq(sorted, sorted); },
327         { cast(void) isPermutation!(eq, Yes.nearlySorted)(sorted, sorted); },
328         { cast(void) isPermutation!(eq, No.nearlySorted)(sorted, sorted); },
329         { cast(void) isPermutation!(eq, Yes.nearlySorted)(sorted, sorted, index); },
330         { cast(void) isPermutation!(eq, Yes.nearlySorted)(sorted, shortSwap[]); },
331         { cast(void) isPermutation!(eq, No.nearlySorted)(sorted, shortSwap[]); },
332         { cast(void) isPermutation!(eq, Yes.nearlySorted)(sorted, shortSwap[], index); },
333         { cast(void) isPermutation!(eq, Yes.nearlySorted)(sorted, longSwap[]); },
334         { cast(void) isPermutation!(eq, No.nearlySorted)(sorted, longSwap[]); },
335         { cast(void) isPermutation!(eq, Yes.nearlySorted)(sorted, longSwap[], index); },
336         { cast(void) isPermutation!(eq, Yes.nearlySorted)(sorted, randomlyShuffled[]); },
337         { cast(void) isPermutation!(eq, No.nearlySorted)(sorted, randomlyShuffled[]); },
338         { cast(void) isPermutation!(eq, No.nearlySorted)(sorted, randomlyShuffled[], index); },
339     )(10_000);
340 
341     writefln!"-- isPermutation!(eq, nearlySorted)(sorted, r2, index):"();
342     writefln!"                  equal(sorted, sorted): %s"(res[0]);
343     writefln!"  r2                index  nearlySorted  took"();
344     writefln!"  sorted            no     yes           %s"(res[1]);
345     writefln!"  sorted            no     no            %s"(res[2]);
346     writefln!"  sorted            yes    yes           %s"(res[3]);
347     writefln!"  shortSwap         no     yes           %s"(res[4]);
348     writefln!"  shortSwap         no     no            %s"(res[5]);
349     writefln!"  shortSwap         yes    yes           %s"(res[6]);
350     writefln!"  longSwap          no     yes           %s"(res[7]);
351     writefln!"  longSwap          no     no            %s"(res[8]);
352     writefln!"  longSwap          yes    yes           %s"(res[9]);
353     writefln!"  randomlyShuffled  no     yes           %s"(res[10]);
354     writefln!"  randomlyShuffled  no     no            %s"(res[11]);
355     writefln!"  randomlyShuffled  yes    no            %s"(res[12]);
356 }
357 
358 
359 private size_t longestCommonPrefix(alias pred, R1, R2)(ref R1 r1, R2 r2)
360 {
361     size_t prefixLength;
362 
363     static if (isRandomAccessRange!R1 && hasSlicing!R1)
364     {
365         const length = min(r1.length, r2.length);
366         while (prefixLength < length && pred(r1[prefixLength], r2[prefixLength]))
367             ++prefixLength;
368 
369         r1 = r1[prefixLength .. $];
370     }
371     else
372     {
373         while (!(r1.empty || r2.empty) && pred(r1.front, r2.front))
374         {
375             r1.popFront();
376             r2.popFront();
377             ++prefixLength;
378         }
379     }
380 
381     return prefixLength;
382 }
383 
384 debug (benchmark) unittest
385 {
386     import std.array : array;
387     import std.datetime.stopwatch;
388     import std.random;
389     import std.range : iota;
390     import std.stdio : writefln;
391 
392     struct ForwardIota
393     {
394         size_t n;
395         size_t i;
396 
397         void popFront() pure nothrow @safe @nogc
398         {
399             assert(!empty, "Attempting to popFront an empty " ~ typeof(this).stringof);
400 
401             ++i;
402         }
403 
404 
405         @property auto front() pure nothrow @safe @nogc
406         {
407             assert(!empty, "Attempting to fetch the front of an empty " ~ typeof(this).stringof);
408 
409             return i;
410         }
411 
412 
413         @property bool empty() const pure nothrow @safe @nogc
414         {
415             return i >= n;
416         }
417 
418 
419         @property ForwardIota save() const pure nothrow @safe @nogc
420         {
421             return ForwardIota(n, i);
422         }
423     }
424 
425     enum size_t n = 64;
426     const size_t[] sortedRA = iota(n).array;
427     auto sortedFR = ForwardIota(n);
428     const size_t[] permuted = iota(n).array.randomShuffle;
429     alias eq = (size_t a, size_t b) => a == b;
430     auto res = benchmark!(
431         {
432             auto r1 = sortedRA[];
433             cast(void) longestCommonPrefix!eq(r1, permuted[]);
434         },
435         {
436             auto r1 = sortedFR.save;
437             cast(void) longestCommonPrefix!eq(r1, permuted[]);
438         },
439     )(10_000);
440 
441 
442     writefln!"random-access interface: %s"(res[0]);
443     writefln!"        range interface: %s"(res[1]);
444 }