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