1 module tupleops;
2 
3 import std.stdio;
4 import std.typecons;
5 
6 template LongestOverload(alias func)
7 {
8     private import std.traits : Parameters;
9 
10     struct S
11     {
12         alias f = func;
13     }
14 
15     enum maxIndex = {
16         size_t maxIdx, maxLen;
17         foreach (i, _f; __traits(getOverloads, S, "f"))
18         {
19             auto plen = Parameters!(_f).length;
20             if (plen > maxLen)
21             {
22                 maxIdx = i;
23                 maxLen = plen;
24             }
25         }
26         return maxIdx;
27     }();
28 
29     static foreach (i, _f; __traits(getOverloads, S, "f"))
30     {
31         static if (i == maxIndex)
32         {
33             alias func = _f;
34             alias Types = Parameters!_f;
35         }
36     }
37 }
38 
39 version (unittest)
40 {
41     void f(int x) {}
42     void f(int x, int y) {}
43     void f(int x, int y, double z) {}
44 }
45 
46 unittest
47 {
48     import std.meta : AliasSeq;
49     static assert(is(LongestOverload!f.Types == AliasSeq!(int, int, double)));
50 
51     alias g = overload!(
52         (int x) {},
53         (int x, int y) {},
54         (int x, int y, double z) {}
55         );
56     static assert(is(LongestOverload!g.Types == AliasSeq!(int, int, double)));
57 }
58 
59 
60 private auto _mapImpl(alias func, Ts...)(return auto ref Ts ts)
61 {
62     alias T = typeof(ts[0]);
63     enum isLongest = is(Tuple!(LongestOverload!func.Types) == T);
64 
65     static if (isTuple!T)
66     {
67         static if (ts.length == 1)
68         {
69             static if (isLongest)
70                 return tuple(func(ts[0].expand));
71             else
72                 return tuple(_mapImpl!func(ts[0].expand));
73         }
74         else
75         {
76             static if (isLongest)
77                 return tuple(func(ts[0].expand),
78                              _mapImpl!func(ts[1 .. $]).expand);
79             else
80                 return tuple(_mapImpl!func(ts[0].expand),
81                              _mapImpl!func(ts[1 .. $]).expand);
82         }
83     }
84     else
85     {
86         static if (ts.length == 1)
87             return tuple(func(ts[0]));
88         else
89             return tuple(func(ts[0]),
90                          _mapImpl!func(ts[1 .. $]).expand);
91     }
92 }
93 
94 /// simple map over tuples
95 auto map(alias func, Ts...)(return auto ref Ts ts)
96 {
97     return _mapImpl!func(ts)[0];
98 }
99 
100 ///
101 unittest
102 {
103     enum t0 = tuple(1, 2, 3);
104     static assert(map!(x => x)(t0) == t0);
105     enum t = tuple(1, 2, tuple(3, tuple(tuple(4, 5), 6), 7));
106     static assert(map!(x => x)(t) == t);
107 
108     assert(map!(x => 2 * x)(t0) == tuple(2, 4, 6));
109     static assert(map!(x => 2 * x)(t) ==
110                   tuple(2, 4, tuple(6, tuple(tuple(8, 10), 12), 14)));
111 
112     static assert(map!((int x, int y) => x + y)(tuple(1, 2)) == 3);
113     static assert(map!((int x, int y) => x + y)(
114                       tuple(tuple(1, 2),
115                             tuple(tuple(3, 4), tuple(5, 6))))
116                   == tuple(3, tuple(7, 11)));
117 }
118 
119 
120 /// equivalent to boost::hana::overload
121 template overload(funcs ...)
122 {
123     static foreach (f; funcs) alias overload = f;
124 }
125 
126 ///
127 unittest
128 {
129     import std.conv : to;
130     alias f = overload!(
131         (int i) => i.to!string,
132         (int i, int j) => i + j,
133         (double d) => d * 2);
134 
135     static assert(f(1, -2) == -1);
136     static assert(f(1.0) == 2.0);
137     static assert(f(1) == "1");
138 
139     enum t = tuple(1.0, 2, tuple(3.0, tuple(tuple(4, 5.0), 6), 7.0));
140     static assert(map!f(t) == tuple(2.0, "2", tuple(6.0, tuple(tuple("4", 10.0), "6"), 14.0)));
141 
142     // apply longest argument overload
143     enum t1 = tuple(1.0, 2, tuple(3, 4));
144     static assert(map!f(t1)  == tuple(2.0, "2", 7));
145 
146     enum t2 = tuple(1.0, 2, tuple(3.0, tuple(tuple(4, 5), 6), 7.0));
147     static assert(map!f(t2)  == tuple(2.0, "2", tuple(6.0, tuple(9, "6"), 14.0)));
148 }
149 
150 
151 /// higher order function for mapping via depth-fisrt search
152 auto depthFirstFlatMap(alias func, Ts ...)(return auto ref Ts ts)
153 {
154     static if (isTuple!(typeof(ts[0])))
155     {
156         static if (ts.length == 1)
157         {
158             return depthFirstFlatMap!func(ts[0].expand);
159         }
160         else
161         {
162             return tuple(depthFirstFlatMap!func(ts[0].expand).expand,
163                          depthFirstFlatMap!func(ts[1..$]).expand);
164         }
165     }
166     else
167     {
168         static if (ts.length == 1)
169         {
170             return tuple(func(ts[0]));
171         }
172         else
173         {
174             return tuple(func(ts[0]),
175                          depthFirstFlatMap!func(ts[1..$]).expand);
176         }
177     }
178 }
179 
180 /// depth-first map iterates by left-to-right search.
181 unittest
182 {
183     enum t = tuple(1, 2, tuple(3, tuple(tuple(4, 5), 6), 7));
184     static assert(depthFirstFlatMap!(x => x)(t) == tuple(1, 2, 3, 4, 5, 6, 7));
185 }
186 
187 auto _foldLeftImpl(alias func, Accumulator, Ts ...)(Accumulator acc, Ts ts)
188 {
189     static if (ts.length == 1)
190     {
191         return func(acc, ts[0]);
192     }
193     else
194     {
195         return _foldLeftImpl!func(func(acc, ts[0]), ts[1 .. $]);
196     }
197 }
198 
199 /// simple fold left over tuples
200 auto foldLeft(alias func, Accumulator, Ts ...)(Accumulator acc, Ts ts)
201 {
202     static if (ts.length == 1 && isTuple!(Ts[0]))
203     {
204         return _foldLeftImpl!func(acc, ts[0].expand);
205     }
206     else
207     {
208         return _foldLeftImpl!func(acc, ts);
209     }
210 }
211 
212 /// depth-first map iterates by left-to-right search.
213 unittest
214 {
215     import std.conv : to;
216 
217     enum t = tuple(1, 2, tuple(3, tuple(tuple(4, 5), 6), 7));
218     static assert(foldLeft!((a, b) => a ~ b.to!string ~ ", ")("", t) ==
219                   "1, 2, Tuple!(int, Tuple!(Tuple!(int, int), int), int)(3, Tuple!(Tuple!(int, int), int)(Tuple!(int, int)(4, 5), 6), 7), ");
220 }
221 
222 /// static map over tuples
223 auto ctMap(alias func, Ts...)(Ts ts)
224 {
225     return foldLeft!(
226         (a, b)
227         {
228             return tuple(a.expand, func!b);
229         })(tuple(), ts);
230 }
231 
232 version (unittest) {
233     enum strof(alias x) = typeof(x).stringof;
234 }
235 
236 ///
237 unittest
238 {
239     enum a = 1;
240     enum b = "b";
241     //     function should be global
242     //     enum strof(alias x) = typeof(x).stringof;
243     static assert(ctMap!strof(a, b) == tuple("int", "string"));
244 }
245 
246 /// static filter using template arg
247 auto ctFilter(alias func, Ts ...)(Ts ts) {
248     return foldLeft!(
249         (a, b)
250         {
251             static if (func!(b))
252             {
253                 return tuple(a.expand, b);
254             }
255             else
256             {
257                 return a;
258             }
259         })(tuple(), ts);
260 }
261 
262 version (unittest) {
263     enum pred(alias b) = isTuple!(typeof(b));
264 }
265 
266 /// static filter example
267 unittest
268 {
269     enum t = tuple(1, 2, tuple(3), tuple(4), 5, tuple(6));
270     // pred should be global
271     // private enum pred(alias b) = isTuple!(typeof(b));
272     static assert(t.ctFilter!pred == tuple(tuple(3), tuple(4), tuple(6)));
273 }
274 
275 version (unittest) {
276     struct T {
277         // int a;
278         // double b;
279         static void f() {}
280         static int g(int i) { return i; }
281     }
282 
283     // alias member(name ...) =  __traits(getMember, T, name[0]);
284     import std.traits : isSomeFunction;
285     enum isMemberFunction(alias name) = isSomeFunction!(__traits(getMember, T, name));
286 }
287 
288 // TODO find nice way to handle AliasSeq
289 unittest
290 {
291     import std.meta;
292     enum names = tuple(__traits(allMembers, T));
293     names.ctMap!isSomeFunction;
294     alias funcs = AliasSeq!(T.f, T.g);
295     // enum memberFunctions = names.ctFilter!isMemberFunction;
296     // memberFunctions.writeln;
297 }
298 
299 /// higher order function for reduction from left via depth-fisrt search
300 auto depthFirstFoldLeft(alias func, Accumulator, Ts ...)(Accumulator acc, auto ref Ts ts)
301 {
302     static if (isTuple!(typeof(ts[0])))
303     {
304         static if (ts.length == 1)
305         {
306             return depthFirstFoldLeft!func(acc, ts[0].expand);
307         }
308         else
309         {
310             return depthFirstFoldLeft!func(depthFirstFoldLeft!func(acc, ts[0].expand),
311                                                ts[1..$]);
312         }
313     }
314     else
315     {
316         static if (ts.length == 1)
317         {
318             return func(acc, ts[0]);
319         }
320         else
321         {
322             return depthFirstFoldLeft!func(func(acc, ts[0]), ts[1..$]);
323         }
324     }
325 }
326 
327 /// depth-first fold left
328 unittest
329 {
330     enum t = tuple(1, 2, tuple(3, tuple(tuple(4, 5), 6), 7));
331     import std.stdio;
332     import std.conv : to;
333     static assert(depthFirstFoldLeft!((a, b) => (a ~ b.to!string))("", t) == "1234567");
334     // TODO(karita) replace depthFirstFlatMap with depthFirstFoldLeft
335     static assert(depthFirstFoldLeft!((a, b) => tuple(a.expand, b))(tuple(), t) == tuple(1, 2, 3, 4, 5, 6, 7));
336 }
337 
338 
339 /// higher order function for mapping via breadth-fisrt search
340 auto breadthFirstFlatMap(alias func, Ts ...)(return auto ref Ts ts)
341 {
342     static if (isTuple!(typeof(ts[0])))
343     {
344         static if (ts.length == 1)
345         {
346             return breadthFirstFlatMap!func(ts[0].expand);
347         }
348         else
349         {
350             return breadthFirstFlatMap!func(ts[1..$], ts[0].expand);
351         }
352     }
353     else
354     {
355         static if (ts.length == 1)
356         {
357             return tuple(func(ts[0]));
358         }
359         else
360         {
361             return tuple(func(ts[0]),
362                          breadthFirstFlatMap!func(ts[1..$]).expand);
363         }
364     }
365 }
366 
367 /// breadth-first map iterates by top-to-bottom search.
368 unittest
369 {
370     enum t = tuple(1,
371                    tuple(
372                        tuple(
373                            4,
374                            tuple(
375                                7)),
376                        2),
377                    tuple(
378                        3,
379                        tuple(
380                            5,
381                            6)));
382     static assert(breadthFirstFlatMap!(x => x)(t) == tuple(1, 2, 3, 4, 5, 6, 7));
383 }
384 
385 /// higher order function for fold left via breadth-fisrt search
386 auto breadthFirstFoldLeft(alias func, Accumulator, Ts ...)(Accumulator acc, auto ref Ts ts)
387 {
388     static if (isTuple!(typeof(ts[0])))
389     {
390         static if (ts.length == 1)
391         {
392             return breadthFirstFoldLeft!func(acc, ts[0].expand);
393         }
394         else
395         {
396             return breadthFirstFoldLeft!func(acc, ts[1..$], ts[0].expand);
397         }
398     }
399     else
400     {
401         static if (ts.length == 1)
402         {
403             return func(acc, ts[0]);
404         }
405         else
406         {
407             return breadthFirstFoldLeft!func(func(acc, ts[0]), ts[1..$]);
408         }
409     }
410 }
411 
412 /// breadth-first map iterates by top-to-bottom search.
413 unittest
414 {
415     enum t = tuple(1,
416                    tuple(
417                          tuple(
418                                4,
419                                tuple(
420                                      7)),
421                          2),
422                    tuple(
423                          3,
424                          tuple(
425                                5,
426                                6)));
427     static assert(breadthFirstFoldLeft!((a, b) => tuple(a.expand, b))(tuple(), t) == tuple(1, 2, 3, 4, 5, 6, 7));
428 }
429 
430 /// flatten nested tuple into 1-d tuple with copies of elements
431 auto flatten(alias map = depthFirstFlatMap, Ts ...)(return auto ref Ts ts)
432 {
433     return map!((return auto ref x) => x)(ts);
434 }
435 
436 ///
437 unittest
438 {
439     enum t = tuple(1, 2, tuple(3, tuple(tuple(4, 5), 6), 7));
440     static assert(t.flatten == tuple(1, 2, 3, 4, 5, 6, 7));
441 }
442 
443 /// flatten nested tuple into 1-d tuple with pointers of elements
444 auto ptrs(alias map = depthFirstFlatMap, Ts ...)(return ref Ts ts)
445 {
446     return map!((return ref x) => &x)(ts);
447 }
448 
449 ///
450 unittest
451 {
452     auto t = tuple(1, 2, tuple(3, tuple(tuple(4, 5), 6), 7));
453     auto p = t.ptrs;
454     *p[1] = 222;
455     auto d = t.flatten;
456     assert(d == tuple(1, 222, 3, 4, 5, 6, 7));
457 
458     static foreach (i; 0 .. t.length) {
459         assert(*p[i] == d[i]);
460     }
461 }
462 
463 ///
464 auto unzip(Z)(Z zipped)
465 {
466     import std.algorithm : map;
467     mixin(
468         {
469             import std.conv : to;
470             alias Tp = typeof(Z.init.front());
471             auto m = "return tuple(";
472             foreach (i; 0 .. Tp.length)
473             {
474                 m ~= "zipped.map!(x => x[" ~ i.to!string ~ "]),";
475             }
476             return m[0 .. $-1] ~ ");";
477         }());
478 }
479 
480 ///
481 unittest
482 {
483     import std.algorithm : equal;
484     import std.range : zip;
485     immutable a = [1, 2, 3];
486     immutable b = ["a", "b", "c"];
487     immutable c = [0.1, 0.2, 0.3];
488 
489     auto x = tuple(a, b, c);
490     auto z = zip(x.expand);
491     auto u = unzip(zip(a, b, c));
492     static foreach (i; 0 .. x.length)
493     {
494         assert(u[i].equal(x[i]));
495     }
496 }