1 /**
2  * ae.utils.range
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.utils.range;
15 
16 import std.range.primitives;
17 import std.typecons;
18 
19 import ae.utils.meta : isDebug;
20 import ae.utils.text.ascii : toDec;
21 
22 /// An equivalent of an array range, but which maintains
23 /// a start and end pointer instead of a start pointer
24 /// and length. This allows .popFront to be faster.
25 /// Optionally, omits bounds checking for even more speed.
26 // TODO: Can we make CHECKED implicit, controlled by
27 //       -release, like regular arrays?
28 // TODO: Does this actually make a difference in practice?
29 //       Run some benchmarks...
30 struct FastArrayRange(T, bool CHECKED=isDebug)
31 {
32 	/// Current head and end.
33 	T* ptr, end;
34 
35 	this(T[] arr)
36 	{
37 		ptr = arr.ptr;
38 		end = ptr + arr.length;
39 	} ///
40 
41 	@property T front()
42 	{
43 		static if (CHECKED)
44 			assert(!empty);
45 		return *ptr;
46 	} ///
47 
48 	void popFront()
49 	{
50 		static if (CHECKED)
51 			assert(!empty);
52 		ptr++;
53 	} ///
54 
55 	@property bool empty() { return ptr==end; } ///
56 
57 	@property ref typeof(this) save() { return this; } ///
58 
59 	T opIndex(size_t index)
60 	{
61 		static if (CHECKED)
62 			assert(index < end-ptr);
63 		return ptr[index];
64 	} ///
65 
66 	T[] opSlice()
67 	{
68 		return ptrSlice(ptr, end);
69 	} ///
70 
71 	T[] opSlice(size_t from, size_t to)
72 	{
73 		static if (CHECKED)
74 			assert(from <= to && to <= end-ptr);
75 		return ptr[from..to];
76 	} ///
77 }
78 
79 auto fastArrayRange(T)(T[] arr) { return FastArrayRange!T(arr); } /// ditto
80 
81 /// Returns a slice for the memory from `a` to `b`.
82 T[] ptrSlice(T)(T* a, T* b)
83 {
84 	return a[0..b-a];
85 }
86 
87 unittest
88 {
89 	FastArrayRange!ubyte r;
90 	auto x = r.save;
91 }
92 
93 // ************************************************************************
94 
95 /// Presents a null-terminated pointer (C-like string) as a range.
96 struct NullTerminated(E)
97 {
98 	E* ptr; /// Current head.
99 	bool empty() { return !*ptr; } ///
100 	ref E front() { return *ptr; } ///
101 	void popFront() { ptr++; } ///
102 	auto save() { return this; } ///
103 }
104 auto nullTerminated(E)(E* ptr)
105 {
106 	return NullTerminated!E(ptr);
107 } /// ditto
108 
109 ///
110 unittest
111 {
112 	void test(S)(S s)
113 	{
114 		import std.utf, std.algorithm.comparison;
115 		assert(equal(s.byCodeUnit, s.ptr.nullTerminated));
116 	}
117 	// String literals are null-terminated
118 	test("foo");
119 	test("foo"w);
120 	test("foo"d);
121 }
122 
123 // ************************************************************************
124 
125 /// Apply a predicate over each consecutive pair.
126 template pairwise(alias pred)
127 {
128 	import std.range : zip, dropOne;
129 	import std.algorithm.iteration : map;
130 	import std.functional : binaryFun;
131 
132 	auto pairwise(R)(R r)
133 	{
134 		return zip(r, r.dropOne).map!(pair => binaryFun!pred(pair[0], pair[1]));
135 	}
136 }
137 
138 ///
139 unittest
140 {
141 	import std.algorithm.comparison : equal;
142 	assert(equal(pairwise!"a+b"([1, 2, 3]), [3, 5]));
143 	assert(equal(pairwise!"b-a"([1, 2, 3]), [1, 1]));
144 }
145 
146 // ************************************************************************
147 
148 /// An infinite variant of `iota`.
149 struct InfiniteIota(T)
150 {
151 	T front; ///
152 	enum empty = false; ///
153 	void popFront() { front++; } ///
154 	T opIndex(T offset) { return front + offset; } ///
155 	InfiniteIota save() { return this; } ///
156 }
157 InfiniteIota!T infiniteIota(T)() { return InfiniteIota!T.init; } /// ditto
158 
159 // ************************************************************************
160 
161 /// Empty range of type E.
162 struct EmptyRange(E)
163 {
164 	@property E front() { assert(false); } ///
165 	void popFront() { assert(false); } ///
166 	@property E back() { assert(false); } ///
167 	void popBack() { assert(false); } ///
168 	E opIndex(size_t) { assert(false); } ///
169 	enum empty = true; ///
170 	enum save = typeof(this).init; ///
171 	enum size_t length = 0; ///
172 }
173 
174 EmptyRange!E emptyRange(E)() { return EmptyRange!E.init; } /// ditto
175 
176 static assert(isInputRange!(EmptyRange!uint));
177 static assert(isForwardRange!(EmptyRange!uint));
178 static assert(isBidirectionalRange!(EmptyRange!uint));
179 static assert(isRandomAccessRange!(EmptyRange!uint));
180 
181 // ************************************************************************
182 
183 /// Like `only`, but evaluates the argument lazily, i.e. when the
184 /// range's "front" is evaluated.
185 /// DO NOT USE before this bug is fixed:
186 /// https://issues.dlang.org/show_bug.cgi?id=11044
187 auto onlyLazy(E)(lazy E value)
188 {
189 	struct Lazy
190 	{
191 		bool empty = false;
192 		@property E front() { assert(!empty); return value; }
193 		void popFront() { assert(!empty); empty = true; }
194 		alias back = front;
195 		alias popBack = popFront;
196 		@property size_t length() { return empty ? 0 : 1; }
197 		E opIndex(size_t i) { assert(!empty); assert(i == 0); return value; }
198 		@property typeof(this) save() { return this; }
199 	}
200 	return Lazy();
201 }
202 
203 static assert(isInputRange!(typeof(onlyLazy(1))));
204 static assert(isForwardRange!(typeof(onlyLazy(1))));
205 static assert(isBidirectionalRange!(typeof(onlyLazy(1))));
206 static assert(isRandomAccessRange!(typeof(onlyLazy(1))));
207 
208 unittest
209 {
210 	import std.algorithm.comparison;
211 	import std.range;
212 
213 	int i;
214 	auto r = onlyLazy(i);
215 	i = 1; assert(equal(r, 1.only));
216 	i = 2; assert(equal(r, 2.only));
217 }
218 
219 // ************************************************************************
220 
221 /// Defer range construction until first empty/front call.
222 auto lazyInitRange(R)(R delegate() constructor)
223 {
224 	bool initialized;
225 	R r = void;
226 
227 	ref R getRange()
228 	{
229 		if (!initialized)
230 		{
231 			r = constructor();
232 			initialized = true;
233 		}
234 		return r;
235 	}
236 
237 	struct LazyRange
238 	{
239 		bool empty() { return getRange().empty; }
240 		auto ref front() { return getRange().front; }
241 		void popFront() { return getRange().popFront; }
242 	}
243 	return LazyRange();
244 }
245 
246 ///
247 unittest
248 {
249 	import std.algorithm.iteration;
250 	import std.range;
251 
252 	int[] todo, done;
253 	chain(
254 		only({ todo = [1, 2, 3]; }),
255 		// eager will fail: todo.map!(n => { done ~= n; }),
256 		lazyInitRange(() => todo.map!(n => { done ~= n; })),
257 	).each!(dg => dg());
258 	assert(done == [1, 2, 3]);
259 }
260 
261 // ************************************************************************
262 
263 /// A faster, random-access version of `cartesianProduct`.
264 auto fastCartesianProduct(R...)(R ranges)
265 {
266 	struct Product
267 	{
268 		size_t start, end;
269 		R ranges;
270 		auto front() { return this[0]; }
271 		void popFront() { start++; }
272 		auto back() { return this[$-1]; }
273 		void popBack() { end--; }
274 		bool empty() const { return start == end; }
275 		size_t length() { return end - start; }
276 		alias opDollar = length;
277 
278 		auto opIndex(size_t index)
279 		{
280 			auto p = start + index;
281 			size_t[R.length] positions;
282 			foreach (i, r; ranges)
283 			{
284 				auto l = r.length;
285 				positions[i] = p % l;
286 				p /= l;
287 			}
288 			assert(p == 0, "Out of bounds");
289 			mixin({
290 				string s;
291 				foreach (i; 0 .. R.length)
292 					s ~= `ranges[` ~ toDec(i) ~ `][positions[` ~ toDec(i) ~ `]], `;
293 				return `return tuple(` ~ s ~ `);`;
294 			}());
295 		}
296 	}
297 	size_t end = 1;
298 	foreach (r; ranges)
299 		end *= r.length;
300 	return Product(0, end, ranges);
301 }
302 
303 unittest
304 {
305 	import std.algorithm.comparison : equal;
306 	assert(fastCartesianProduct().length == 1);
307 	assert(fastCartesianProduct([1, 2, 3]).equal([tuple(1), tuple(2), tuple(3)]));
308 	assert(fastCartesianProduct([1, 2], [3, 4, 5]).equal([
309 		tuple(1, 3), tuple(2, 3),
310 		tuple(1, 4), tuple(2, 4),
311 		tuple(1, 5), tuple(2, 5),
312 	]));
313 }
314 
315 // ************************************************************************
316 
317 /// Calculate the mean value of the range's elements (sum divided by length).
318 /// The range must obviously be non-empty.
319 auto average(R)(R range) if (hasLength!R)
320 {
321 	import std.algorithm.iteration : sum;
322 	return sum(range) / range.length;
323 }
324 
325 unittest
326 {
327 	assert([1, 2, 3].average == 2);
328 }
329 
330 // ************************************************************************
331 
332 /// `map` variant which takes the predicate as a functor.
333 /// https://forum.dlang.org/post/qnigarkuxxnqwdernhzv@forum.dlang.org
334 struct PMap(R, Pred)
335 {
336 	R r; /// Source range.
337 	Pred pred; /// The predicate.
338 
339 	auto ref front() { return pred(r.front); } ///
340 	static if (__traits(hasMember, R, "back"))
341 		auto ref back() { return pred(r.back); } ///
342 
343 	alias r this;
344 }
345 auto pmap(R, Pred)(R r, Pred pred) { return PMap!(R, Pred)(r, pred); } /// ditto
346 
347 unittest
348 {
349 	import std.algorithm.comparison : equal;
350 	import std.range : iota;
351 	assert(5.iota.pmap((int n) => n + 1).equal([1, 2, 3, 4, 5]));
352 }