1 /**
2     Some additional range functions.
3 
4     Copyright: © 2018 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 dentist.util.range;
10 
11 import std.functional : unaryFun;
12 import std.meta : AliasSeq, staticMap;
13 import std.range : ElementType, isInputRange;
14 import std.traits : rvalueOf;
15 import std.typecons : tuple, Tuple;
16 
17 /**
18     This range iterates over fixed-sized chunks of size chunkSize of a source
19     range. Source must be an input range. chunkSize must be greater than zero.
20 
21     See Also: `std.range.chunks`
22     Returns: Range of chunks, ie. `ElementType!Source[]`.
23 */
24 auto arrayChunks(Source)(Source range, in size_t chunkSize) if (isInputRange!Source)
25 {
26     alias Element = ElementType!Source;
27 
28     static struct ArrayChunks
29     {
30         private Source range;
31         private const size_t chunkSize;
32         private Element[] chunk;
33 
34         this(Source range, in size_t chunkSize)
35         {
36             this.range = range;
37             this.chunkSize = chunkSize;
38             this.popFront();
39         }
40 
41         void popFront()
42         {
43             if (range.empty)
44             {
45                 chunk = null;
46 
47                 return;
48             }
49 
50             chunk = new Element[chunkSize];
51 
52             foreach (i; 0 .. chunkSize)
53             {
54                 chunk[i] = range.front;
55 
56                 if (range.empty)
57                 {
58                     chunk = chunk[0 .. i + 1];
59                     break;
60                 }
61                 else
62                 {
63                     range.popFront();
64                 }
65             }
66         }
67 
68         @property Element[] front()
69         {
70             return chunk;
71         }
72 
73         @property bool empty()
74         {
75             return chunk is null;
76         }
77     }
78 
79     assert(chunkSize > 0, "chunkSize must be greater than zero");
80 
81     return ArrayChunks(range, chunkSize);
82 }
83 
84 ///
85 unittest
86 {
87     import std.array : array;
88     import std.range : iota;
89 
90     auto chunks = iota(10).arrayChunks(2);
91     assert(chunks.array == [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]);
92 }
93 
94 /// Generate a tuple of tuples of chunkSize.
95 template chunks(size_t chunkSize)
96 {
97     auto chunks(T...)(T args) pure nothrow @safe if (args.length >= chunkSize)
98     {
99         return tuple(tuple(args[0 .. chunkSize]), chunks(args[chunkSize .. $]).expand);
100     }
101 
102     auto chunks(T...)(T args) pure nothrow @safe
103             if (0 < args.length && args.length < chunkSize)
104     {
105         return tuple(tuple(args[0 .. $]));
106     }
107 
108     auto chunks(T...)(T args) pure nothrow @safe if (args.length == 0)
109     {
110         return tuple();
111     }
112 }
113 
114 ///
115 unittest
116 {
117     auto c1 = chunks!2(0, 1, 2, 3, 4, 5);
118 
119     assert(c1 == tuple(tuple(0, 1), tuple(2, 3), tuple(4, 5)));
120 
121     auto c2 = chunks!3(false, "1", 2.0, 3, '4', 5);
122 
123     assert(c2 == tuple(tuple(false, "1", 2.0), tuple(3, '4', 5)));
124 
125     enum c4 = chunks!4(false, "1", 2.0, 3, '4', 5);
126 
127     static assert(c4 == tuple(tuple(false, "1", 2.0, 3), tuple('4', 5)));
128 }
129 
130 /// Split a list of aliases into chunks.
131 template Chunks(size_t chunkSize, T...)
132 {
133     static if (T.length >= chunkSize)
134     {
135         alias Chunks = AliasSeq!(Chunk!(T[0 .. chunkSize]), Chunks!(chunkSize, T[chunkSize .. $]));
136     }
137     else static if (0 < T.length && T.length < chunkSize)
138     {
139         alias Chunks = AliasSeq!(Chunk!(T[0 .. $]));
140     }
141     else static if (T.length == 0)
142     {
143         alias Chunks = AliasSeq!();
144     }
145     else
146     {
147         static assert(0);
148     }
149 }
150 
151 template Chunk(T...)
152 {
153     struct Chunk
154     {
155         alias chunks = T;
156     }
157 }
158 
159 ///
160 unittest
161 {
162     alias c1 = Chunks!(2, AliasSeq!(int, int, int, int, int, int));
163 
164     static assert(is(c1 == AliasSeq!(Chunk!(int, int), Chunk!(int, int), Chunk!(int, int))));
165     static foreach (pair; c1)
166     {
167         static foreach (type; pair.chunks)
168         {
169             static assert(is(type == int));
170         }
171     }
172 }
173 
174 /*
175     Build a comparator according to `pred`.
176 */
177 template Comparator(pred...) if (pred.length == 1)
178 {
179     /// Return comparison value akin to `opCmp`.
180     int compare(S, T = S)(in S a, in T b)
181     {
182         alias _pred = unaryFun!pred;
183         auto _a = _pred(a);
184         auto _b = _pred(b);
185 
186         if (_a < _b)
187             return -1;
188         else if (_a == _b)
189             return 0;
190         else
191             return 1;
192     }
193 
194     /// Return `true` iff `a < b`.
195     bool lt(S, T = S)(in S a, in T b)
196     {
197         return compare!(S, T)(a, b) < 0;
198     }
199 
200     /// Return `true` iff `a <= b`.
201     bool le(S, T = S)(in S a, in T b)
202     {
203         return compare!(S, T)(a, b) <= 0;
204     }
205 
206     /// Return `true` iff `a == b`.
207     bool eq(S, T = S)(in S a, in T b)
208     {
209         return compare!(S, T)(a, b) == 0;
210     }
211 
212     /// Return `true` iff `a >= b`.
213     bool ge(S, T = S)(in S a, in T b)
214     {
215         return compare!(S, T)(a, b) >= 0;
216     }
217 
218     /// Return `true` iff `a > b`.
219     bool gt(S, T = S)(in S a, in T b)
220     {
221         return compare!(S, T)(a, b) > 0;
222     }
223 }
224 
225 ///
226 unittest
227 {
228     alias compareSquares = Comparator!"a ^^ 2".compare;
229 
230     assert(compareSquares(1, 2) < 0);
231     assert(compareSquares(1, -2) < 0);
232     assert(compareSquares(-1, 1) == 0);
233     assert(compareSquares(-2.0, 1) > 0);
234 
235     alias compareByLength = Comparator!"a.length".compare;
236 
237     assert(compareByLength([], [1]) < 0);
238     assert(compareByLength([1, 2], [1]) > 0);
239     assert(compareByLength([1, 2], ["1", "2"]) == 0);
240 
241     alias compareAbsInts = Comparator!("a > 0 ? a : -a").compare!(int);
242 
243     assert(compareSquares(1, 2) < 0);
244     assert(compareSquares(1, -2) < 0);
245     assert(compareSquares(-1, 1) == 0);
246     assert(compareSquares(-2, 1) > 0);
247     static assert(!__traits(compiles, compareAbsInts(-2.0, 1.0)));
248 
249     alias ltSquared = Comparator!("a ^^ 2").lt;
250 
251     assert(ltSquared(1, 2));
252     assert(ltSquared(1, -2));
253     assert(!ltSquared(-2, -1));
254 
255     alias eqSquared = Comparator!("a ^^ 2").eq;
256 
257     assert(eqSquared(1, 1));
258     assert(eqSquared(1, -1));
259     assert(!eqSquared(1, 2));
260 }
261 
262 /// Take exactly `n` element from range. Throws an exception if range has not
263 /// enough  elements.
264 ///
265 /// Throws: Exception if range has less than `n` elements.
266 ElementType!R[n] takeExactly(size_t n, R)(R range) if (isInputRange!R)
267 {
268     import std.exception : enforce;
269 
270     ElementType!R[n] result;
271     size_t i = 0;
272 
273     while (!range.empty && i < n)
274     {
275         result[i++] = range.front;
276         range.popFront();
277     }
278 
279     enforce!Exception(i == n, "not enough elements");
280 
281     return result;
282 }
283 
284 ///
285 unittest
286 {
287     import std.exception : assertThrown;
288     import std.range : iota;
289 
290     static assert(is(typeof(iota(10).takeExactly!5) == int[5]));
291     assert(iota(10).takeExactly!5 == [0, 1, 2, 3, 4]);
292 
293     assertThrown!Exception(iota(2).takeExactly!5);
294 }
295 
296 class WrapLinesImpl(R)
297 {
298     R output;
299     size_t lineWidth;
300     size_t column;
301 
302     this(R output, size_t lineWidth)
303     {
304         this.output = output;
305         this.lineWidth = lineWidth;
306     }
307 
308     void put(inout(char) c)
309     {
310         import std.range.primitives;
311 
312         assert(c == '\n' || column < lineWidth);
313 
314         std.range.primitives.put(output, c);
315         ++column;
316 
317         if (c == '\n')
318         {
319             column = 0;
320         }
321 
322         if (column >= lineWidth)
323         {
324             put('\n');
325         }
326     }
327 
328     void put(string chunk)
329     {
330         foreach (c; chunk)
331         {
332             put(c);
333         }
334     }
335 }
336 
337 auto wrapLines(R)(R output, size_t lineWidth)
338 {
339     return new WrapLinesImpl!R(output, lineWidth);
340 }
341 
342 unittest
343 {
344     import std.range.primitives : put;
345 
346     auto outputBuffer = new dchar[12];
347     auto output = wrapLines(outputBuffer, 10);
348 
349     put(output, "hello world");
350 
351     assert(outputBuffer == "hello worl\nd");
352 }
353 
354 /// Return a tuple of `fun` applied to each value of `tuple`.
355 auto tupleMap(alias fun, Types...)(in Types values)
356 {
357     alias mapper = unaryFun!fun;
358     alias MappedValue(V) = typeof(mapper(rvalueOf!V));
359     alias MappedTuple = Tuple!(staticMap!(MappedValue, Types));
360 
361     MappedTuple mappedTuple;
362 
363     static foreach (i; 0 .. Types.length)
364     {
365         mappedTuple[i] = mapper(values[i]);
366     }
367 
368     return mappedTuple;
369 }
370 
371 ///
372 unittest
373 {
374     import std.conv : to;
375     import std.typecons : tuple;
376 
377     assert(
378         tupleMap!"2*a"(1, 2, 3.0) ==
379         tuple(2, 4, 6.0)
380     );
381     assert(
382         tupleMap!(x => to!string(x))(1, '2', 3.0) ==
383         tuple("1", "2", "3")
384     );
385 }