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 // TODO move to ae.utils.array
82 /// Returns a slice for the memory from `a` to `b`.
83 T[] ptrSlice(T)(T* a, T* b)
84 {
85 	return a[0..b-a];
86 }
87 
88 unittest
89 {
90 	FastArrayRange!ubyte r;
91 	auto x = r.save;
92 }
93 
94 // ************************************************************************
95 
96 /// Presents a null-terminated pointer (C-like string) as a range.
97 struct NullTerminatedPtrRange(E)
98 {
99 	E* ptr; /// Current head.
100 	bool empty() { return !*ptr; } ///
101 	ref E front() { return *ptr; } ///
102 	void popFront() { ptr++; } ///
103 	auto save() { return this; } ///
104 }
105 auto nullTerminatedPtrRange(E)(E* ptr)
106 {
107 	return NullTerminatedPtrRange!E(ptr);
108 } /// ditto
109 
110 ///
111 unittest
112 {
113 	void test(S)(S s)
114 	{
115 		import std.utf : byCodeUnit;
116 		import std.algorithm.comparison : equal;
117 		assert(equal(s.byCodeUnit, s.ptr.nullTerminatedPtrRange));
118 	}
119 	// String literals are null-terminated
120 	test("foo");
121 	test("foo"w);
122 	test("foo"d);
123 }
124 
125 deprecated alias NullTerminated = NullTerminatedPtrRange;
126 deprecated alias nullTerminated = nullTerminatedPtrRange;
127 
128 // ************************************************************************
129 
130 /// Apply a predicate over each consecutive pair.
131 template pairwise(alias pred)
132 {
133 	import std.range : zip, dropOne;
134 	import std.algorithm.iteration : map;
135 	import std.functional : binaryFun;
136 
137 	auto pairwise(R)(R r)
138 	{
139 		return zip(r, r.dropOne).map!(pair => binaryFun!pred(pair[0], pair[1]));
140 	}
141 }
142 
143 ///
144 unittest
145 {
146 	import std.algorithm.comparison : equal;
147 	assert(equal(pairwise!"a+b"([1, 2, 3]), [3, 5]));
148 	assert(equal(pairwise!"b-a"([1, 2, 3]), [1, 1]));
149 }
150 
151 // ************************************************************************
152 
153 /// An infinite variant of `iota`.
154 struct InfiniteIota(T)
155 {
156 	T front; ///
157 	enum empty = false; ///
158 	void popFront() { front++; } ///
159 	T opIndex(T offset) { return front + offset; } ///
160 	InfiniteIota save() { return this; } ///
161 }
162 InfiniteIota!T infiniteIota(T)() { return InfiniteIota!T.init; } /// ditto
163 
164 // ************************************************************************
165 
166 /// Empty range of type E.
167 struct EmptyRange(E)
168 {
169 	@property E front() { assert(false); } ///
170 	void popFront() { assert(false); } ///
171 	@property E back() { assert(false); } ///
172 	void popBack() { assert(false); } ///
173 	E opIndex(size_t) { assert(false); } ///
174 	enum empty = true; ///
175 	enum save = typeof(this).init; ///
176 	enum size_t length = 0; ///
177 }
178 
179 EmptyRange!E emptyRange(E)() { return EmptyRange!E.init; } /// ditto
180 
181 static assert(isInputRange!(EmptyRange!uint));
182 static assert(isForwardRange!(EmptyRange!uint));
183 static assert(isBidirectionalRange!(EmptyRange!uint));
184 static assert(isRandomAccessRange!(EmptyRange!uint));
185 
186 // ************************************************************************
187 
188 /// Like `only`, but evaluates the argument lazily, i.e. when the
189 /// range's "front" is evaluated.
190 /// DO NOT USE before this bug is fixed:
191 /// https://issues.dlang.org/show_bug.cgi?id=11044
192 auto onlyLazy(E)(lazy E value)
193 {
194 	struct Lazy
195 	{
196 		bool empty = false;
197 		@property E front() { assert(!empty); return value; }
198 		void popFront() { assert(!empty); empty = true; }
199 		alias back = front;
200 		alias popBack = popFront;
201 		@property size_t length() { return empty ? 0 : 1; }
202 		E opIndex(size_t i) { assert(!empty); assert(i == 0); return value; }
203 		@property typeof(this) save() { return this; }
204 	}
205 	return Lazy();
206 }
207 
208 static assert(isInputRange!(typeof(onlyLazy(1))));
209 static assert(isForwardRange!(typeof(onlyLazy(1))));
210 static assert(isBidirectionalRange!(typeof(onlyLazy(1))));
211 static assert(isRandomAccessRange!(typeof(onlyLazy(1))));
212 
213 unittest
214 {
215 	import std.algorithm.comparison;
216 	import std.range;
217 
218 	int i;
219 	auto r = onlyLazy(i);
220 	i = 1; assert(equal(r, 1.only));
221 	i = 2; assert(equal(r, 2.only));
222 }
223 
224 // ************************************************************************
225 
226 /// Defer range construction until first empty/front call.
227 auto lazyInitRange(R)(R delegate() constructor)
228 {
229 	bool initialized;
230 	R r = void;
231 
232 	ref R getRange()
233 	{
234 		if (!initialized)
235 		{
236 			r = constructor();
237 			initialized = true;
238 		}
239 		return r;
240 	}
241 
242 	struct LazyRange
243 	{
244 		bool empty() { return getRange().empty; }
245 		auto ref front() { return getRange().front; }
246 		void popFront() { return getRange().popFront; }
247 	}
248 	return LazyRange();
249 }
250 
251 ///
252 unittest
253 {
254 	import std.algorithm.iteration;
255 	import std.range;
256 
257 	int[] todo, done;
258 	chain(
259 		only({ todo = [1, 2, 3]; }),
260 		// eager will fail: todo.map!(n => (){ done ~= n; }),
261 		lazyInitRange(() => todo.map!(n => (){ done ~= n; })),
262 	).each!(dg => dg());
263 	assert(done == [1, 2, 3]);
264 }
265 
266 // ************************************************************************
267 
268 /// A faster, random-access version of `cartesianProduct`.
269 auto fastCartesianProduct(R...)(R ranges)
270 {
271 	struct Product
272 	{
273 		size_t start, end;
274 		R ranges;
275 		auto front() { return this[0]; }
276 		void popFront() { start++; }
277 		auto back() { return this[$-1]; }
278 		void popBack() { end--; }
279 		bool empty() const { return start == end; }
280 		size_t length() { return end - start; }
281 		alias opDollar = length;
282 
283 		auto opIndex(size_t index)
284 		{
285 			auto p = start + index;
286 			size_t[R.length] positions;
287 			foreach (i, r; ranges)
288 			{
289 				auto l = r.length;
290 				positions[i] = p % l;
291 				p /= l;
292 			}
293 			assert(p == 0, "Out of bounds");
294 			mixin({
295 				string s;
296 				foreach (i; 0 .. R.length)
297 					s ~= `ranges[` ~ toDec(i) ~ `][positions[` ~ toDec(i) ~ `]], `;
298 				return `return tuple(` ~ s ~ `);`;
299 			}());
300 		}
301 	}
302 	size_t end = 1;
303 	foreach (r; ranges)
304 		end *= r.length;
305 	return Product(0, end, ranges);
306 }
307 
308 unittest
309 {
310 	import std.algorithm.comparison : equal;
311 	assert(fastCartesianProduct().length == 1);
312 	assert(fastCartesianProduct([1, 2, 3]).equal([tuple(1), tuple(2), tuple(3)]));
313 	assert(fastCartesianProduct([1, 2], [3, 4, 5]).equal([
314 		tuple(1, 3), tuple(2, 3),
315 		tuple(1, 4), tuple(2, 4),
316 		tuple(1, 5), tuple(2, 5),
317 	]));
318 }
319 
320 // ************************************************************************
321 
322 /// Calculate the mean value of the range's elements (sum divided by length).
323 /// The range must obviously be non-empty.
324 auto average(R)(R range) if (hasLength!R)
325 {
326 	import std.algorithm.iteration : sum;
327 	return sum(range) / range.length;
328 }
329 
330 unittest
331 {
332 	assert([1, 2, 3].average == 2);
333 }
334 
335 // ************************************************************************
336 
337 static import ae.utils.functor.algorithm;
338 deprecated alias pmap = ae.utils.functor.algorithm.map;