1 /**
2   Some helper functions for ranges
3 */
4 module ggplotd.range;
5 
6 version(unittest) {
7     import dunit.toolkit;
8 }
9 
10 private struct HashSet(E) {
11     // TODO switch to proper implementation (not using AA)
12     bool put(E el)
13     {
14         if ( el in set )
15             return false;
16         set[el] = set.length;
17         return true;
18     }
19 
20     import std.range : ElementType;
21 
22     bool put(R)(R range)
23         if (is(E==ElementType!R))
24     {
25         auto result = false;
26         foreach(el; range)
27         {
28             auto r = this.put(el);
29             if (r)
30                 result = true;
31         }
32         return result;
33     }
34 
35     bool put(S)(in S hset)
36         if (is(S==HashSet!E))
37     {
38         return this.put(hset.data);
39     }
40 
41     @property auto data() const
42     {
43         import std.array : array;
44         import std.algorithm : map, sort;
45         auto kv = set.byKeyValue().array;
46         auto sorted = kv.sort!((a, b) => a.value < b.value);
47         return sorted.map!((a) => a.key);
48     }
49     
50     auto @property length() const
51     {
52         return set.length;
53     }
54 
55     size_t[E] set;
56 }
57 
58 unittest
59 {
60     import std.array : array;
61     import std.range : walkLength;
62     HashSet!string set;
63     set.put("b");
64     assertEqual(set.data.walkLength, 1);
65     set.put("b");
66     assertEqual(set.data.walkLength, 1);
67     set.put(["a","b"]);
68     assertEqual(set.data.walkLength, 2);
69     assertEqual(set.data.array, ["b", "a"]);
70     HashSet!string set2;
71     set2.put(["a", "b", "c", "d", "e", "f", "g", "h", "i"]);
72     set.put(set2);
73     assertEqual(set.data.array, ["b", "a", "c", "d", "e", "f", "g", "h", "i"]);
74 }
75 
76 /**
77   Lazily iterate over range and returning only uniques
78   */
79 auto uniquer(R)(R range)
80 {
81     struct Unique(Range)
82     {
83         import std.range : ElementType;
84 
85         Range range;
86         HashSet!(ElementType!Range) set;
87 
88         this( Range _range )
89         {
90             import std.range : front;
91             range = _range;
92             this.popFront;
93         }
94 
95         @property auto front()
96         {
97             import std.range : front;
98             return range.front;
99         }
100 
101         void popFront()
102         {
103             import std.range : front, popFront, empty;
104             while( !range.empty )
105             {
106                 // TODO: Currently this causes an unnecessary "initial" check, because we now that previous value was already added. I think the only way to solve this would be to keep currentValue and possible future value around.
107                 if (set.put(range.front))
108                 {
109                     currentFront = range.front;
110                     break;
111                 }
112                 range.popFront;
113             }
114         }
115 
116         @property bool empty()
117         {
118             import std.range : empty;
119             return range.empty;
120         }
121 
122         ElementType!Range currentFront;
123     }
124     return Unique!R(range);
125 }
126 
127 ///
128 unittest
129 {
130     import std.array : array;
131     assertEqual( [1,1,1,1].uniquer.array, [1] );
132     assertEqual( [1].uniquer.array, [1] );
133     assertEqual( "".uniquer.array, [] );
134     assertEqual( [1,2,1,3].uniquer.array, [1,2,3] );
135     assertEqual( [1,2,1,3,1,2].uniquer.array, [1,2,3] );
136     assertEqual( [1,2,3].uniquer.array, [1,2,3] );
137     assertEqual( ["a","b","a","c","a","c"].uniquer.array, ["a","b","c"] );
138 
139     import std.typecons : tuple;
140     assertEqual( [tuple(1,"a"),tuple(1,"b"),tuple(2,"b"),tuple(1,"b")]
141             .uniquer.array, [tuple(1,"a"),tuple(1,"b"),tuple(2,"b")] );
142 }
143 
144 /++
145     Group an (unsorted) range by the result of the function applied to each element
146 +/
147 private import std.range : isInputRange;
148 
149 auto groupBy(alias func = function(a) { return a; }, R)(R values)
150     if (isInputRange!R)
151 {
152     import std.range : front, ElementType;
153     alias K = typeof(func(values.front));
154     alias V = ElementType!R[];
155     V[K] grouped;
156     foreach(value; values) 
157         grouped[func(value)] ~= value;
158     return grouped;
159 }
160 
161 ///
162 unittest {
163     import std.stdio : writeln;
164     import std.algorithm : sort;
165     import std.typecons : tuple;
166     auto xs = [
167         tuple("a", 1.0),
168         tuple("b", 3.0),
169         tuple("a", 2.0),
170         tuple("b", 4.0)];
171     auto grouped = xs.groupBy!((a) => a[0]);
172     assertEqual( grouped.keys.length, 2 );
173     assertEqual( grouped.keys.sort(), ["a","b"].sort() );
174 }
175 
176 /++
177   Merge the elements of two ranges. If first is not a range then merge that with each element of the second range and vice versa.
178 +/
179 auto mergeRange( R1, R2 )( R1 r1, R2 r2 )
180     if (isInputRange!R1 || isInputRange!R2)
181 {
182     import std.range : zip, StoppingPolicy, walkLength, repeat;
183     import std.algorithm : map;
184 
185     import ggplotd.aes : merge;
186     static if (isInputRange!R1 && isInputRange!R2)
187         return zip(StoppingPolicy.longest, r1,r2).map!((a) => a[0].merge( a[1] ) );
188     else
189     {
190         // TODO: should not have to repeat r2 for more than once with stoppingpolicy.longest,
191         // but currently doing that crashes. Probably compiler bug, might try changing it later
192         static if (isInputRange!R1)
193             return zip(StoppingPolicy.longest, r1, r2.repeat(r1.walkLength))
194                 .map!((a) => a[0].merge( a[1] ) );
195         else
196             return zip(StoppingPolicy.longest, r1.repeat(r2.walkLength), r2 )
197                 .map!((a) => a[0].merge( a[1] ) );
198     }
199 }
200 
201 unittest
202 {
203     import std.range : walkLength;
204     import ggplotd.aes : Aes, DefaultValues;
205     auto m = [DefaultValues].mergeRange( Aes!(double[], "x")([1.0]));
206     assertEqual( m.walkLength, 1);
207 }
208 
209 
210 ///
211 unittest
212 {
213     import std.range : front;
214     import ggplotd.aes : Aes, DefaultValues;
215 
216     auto xs = ["a", "b"];
217     auto ys = ["c", "d"];
218     auto labels = ["e", "f"];
219     auto aes = Aes!(string[], "x", string[], "y", string[], "label")(xs, ys, labels);
220     auto nlAes = mergeRange(DefaultValues, aes );
221     assertEqual(nlAes.front.x, "a");
222     assertEqual(nlAes.front.label, "e");
223     assertEqual(nlAes.front.colour, "black");
224     auto nlAes2 = aes.mergeRange(DefaultValues);
225     assertEqual(nlAes2.front.x, "a");
226     assertEqual(nlAes2.front.label, "");
227     assertEqual(nlAes2.front.colour, "black");
228 }