1 //
2 // Tuplize multiple iterators.
3 // It takes iterators and builds on them a new iterator from tuples of
4 // aggregated iterators.
5 // 
6 
7 module vest.range.tuplizer;
8 
9 import std.range      : empty, popFront, front;
10 import std.typecons   : Tuple, tuple;
11 import std.functional : forward;
12 import std.conv       : to;
13 
14 auto tuplizer(Ranges...)(auto ref Ranges ranges)
15 {
16     static assert(Ranges.length, "don't call with emprty args");
17     return Tuplizer!Ranges(forward!ranges);
18 }
19 
20 private struct Tuplizer(Ranges...)
21 {
22 private:
23     Tuple!(Ranges) _ranges;
24 public:
25     this(Ranges ranges)
26     {
27         _ranges = tuple(ranges);
28     }
29 
30     @property
31     bool empty() const
32     {
33         // _ranges[0].empty || _ranges[1].empty ...
34         return mixin(_ct_join!(Ranges.length, "empty", " || "));
35     }
36 
37     @property
38     auto front() const
39     {
40         // tuple(_ranges[0].front, _ranges[1].front, ...)
41         return mixin("tuple(" ~ _ct_join!(Ranges.length, "front", ", ") ~ ")");
42     }
43     
44     @property
45     auto front() 
46     {
47         // tuple(_ranges[0].front, _ranges[1].front, ...)
48         return mixin("tuple(" ~ _ct_join!(Ranges.length, "front", ", ") ~ ")");
49     }
50 
51     // Remove the first element
52     void popFront()
53     {
54         // _ranges[0].popFront; _ranges[1].popFront; ...
55         mixin(_ct_join!(Ranges.length, "popFront", "; ") ~ ";");
56     }
57 
58     private template _ct_join(size_t len, string caller, string glue) 
59     {
60         static assert(len, "len mast be greater than zero");
61         static if(0 == len) {
62             enum string _ct_join = "";
63         } else static if(1 == len) {
64             enum string _ct_join = "_ranges[0]." ~ caller;
65         } else {
66             enum string _ct_join = 
67                 _ct_join!(len-1, caller, glue)          ~
68                 glue                                  ~
69                 "_ranges[" ~ (len-1).to!string ~ "]." ~ 
70                 caller;
71         }
72     }
73 
74     // just for test
75     //pragma(msg, _ct_join!(Ranges.length, "caller()", " ;glue; ") );
76     //pragma(msg, _ct_join!(Ranges.length, "front", ", ") );
77     //pragma(msg, _ct_join!(Ranges.length, "popFront", "; ") ~ ";" );
78 }
79 
80 
81 // cd source 
82 // rdmd -unittest -main  vest/range/tuplizer
83 unittest {
84     import std.typecons  : tuple, Tuple;
85     import std.range     : iota;
86     import std.array     : array;
87     import std.algorithm : map, equal;
88 
89     auto rf = iota(0.5, 0.0, -0.1); // 0.5, 0.4, 0.3, 0.2, 0.1
90     auto ri = iota(50, 101, 10);    // 50,  60,  70,  80,  90,  100
91     auto as = ["str1", "str2", "str3"];
92     
93 
94     auto rt = tuplizer(ri, rf, as);
95     Tuple!(int, double, string)[] at = [
96         tuple(50, 0.5, "str1"),
97         tuple(60, 0.4, "str2"),
98         tuple(70, 0.3, "str3")
99     ];
100 
101     assert(rt.equal(at));
102     assert(rt.map!"a[2]".equal(as));
103     assert(rt.map!"a[0]".equal(ri.array[0..3]) );
104 }