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 <vladimir@thecybershadow.net>
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 	T* ptr, end;
33 
34 	this(T[] arr)
35 	{
36 		ptr = arr.ptr;
37 		end = ptr + arr.length;
38 	}
39 
40 	@property T front()
41 	{
42 		static if (CHECKED)
43 			assert(!empty);
44 		return *ptr;
45 	}
46 
47 	void popFront()
48 	{
49 		static if (CHECKED)
50 			assert(!empty);
51 		ptr++;
52 	}
53 
54 	@property bool empty() { return ptr==end; }
55 
56 	@property ref typeof(this) save() { return this; }
57 
58 	T opIndex(size_t index)
59 	{
60 		static if (CHECKED)
61 			assert(index < end-ptr);
62 		return ptr[index];
63 	}
64 
65 	T[] opSlice()
66 	{
67 		return ptrSlice(ptr, end);
68 	}
69 
70 	T[] opSlice(size_t from, size_t to)
71 	{
72 		static if (CHECKED)
73 			assert(from <= to && to <= end-ptr);
74 		return ptr[from..to];
75 	}
76 }
77 
78 auto fastArrayRange(T)(T[] arr) { return FastArrayRange!T(arr); }
79 
80 T[] ptrSlice(T)(T* a, T* b)
81 {
82 	return a[0..b-a];
83 }
84 
85 unittest
86 {
87 	FastArrayRange!ubyte r;
88 	auto x = r.save;
89 }
90 
91 // ************************************************************************
92 
93 /// Presents a null-terminated pointer (C-like string) as a range.
94 struct NullTerminated(E)
95 {
96 	E* ptr;
97 	bool empty() { return !*ptr; }
98 	ref E front() { return *ptr; }
99 	void popFront() { ptr++; }
100 	auto save() { return this; }
101 }
102 auto nullTerminated(E)(E* ptr)
103 {
104 	return NullTerminated!E(ptr);
105 }
106 
107 unittest
108 {
109 	void test(S)(S s)
110 	{
111 		import std.utf, std.algorithm.comparison;
112 		assert(equal(s.byCodeUnit, s.ptr.nullTerminated));
113 	}
114 	// String literals are null-terminated
115 	test("foo");
116 	test("foo"w);
117 	test("foo"d);
118 }
119 
120 // ************************************************************************
121 
122 /// Apply a predicate over each consecutive pair.
123 template pairwise(alias pred)
124 {
125 	import std.range : zip, dropOne;
126 	import std.algorithm.iteration : map;
127 	import std.functional : binaryFun;
128 
129 	auto pairwise(R)(R r)
130 	{
131 		return zip(r, r.dropOne).map!(pair => binaryFun!pred(pair[0], pair[1]));
132 	}
133 }
134 
135 ///
136 unittest
137 {
138 	import std.algorithm.comparison : equal;
139 	assert(equal(pairwise!"a+b"([1, 2, 3]), [3, 5]));
140 	assert(equal(pairwise!"b-a"([1, 2, 3]), [1, 1]));
141 }
142 
143 // ************************************************************************
144 
145 struct InfiniteIota(T)
146 {
147 	T front;
148 	enum empty = false;
149 	void popFront() { front++; }
150 	T opIndex(T offset) { return front + offset; }
151 	InfiniteIota save() { return this; }
152 }
153 InfiniteIota!T infiniteIota(T)() { return InfiniteIota!T.init; }
154 
155 // ************************************************************************
156 
157 /// Empty range of type E.
158 struct EmptyRange(E)
159 {
160 	@property E front() { assert(false); }
161 	void popFront() { assert(false); }
162 	@property E back() { assert(false); }
163 	void popBack() { assert(false); }
164 	E opIndex(size_t) { assert(false); }
165 	enum empty = true;
166 	enum save = typeof(this).init;
167 	enum size_t length = 0;
168 }
169 
170 /// ditto
171 EmptyRange!E emptyRange(E)() { return EmptyRange!E.init; }
172 
173 static assert(isInputRange!(EmptyRange!uint));
174 static assert(isForwardRange!(EmptyRange!uint));
175 static assert(isBidirectionalRange!(EmptyRange!uint));
176 static assert(isRandomAccessRange!(EmptyRange!uint));
177 
178 // ************************************************************************
179 
180 /// Like `only`, but evaluates the argument lazily, i.e. when the
181 /// range's "front" is evaluated.
182 /// DO NOT USE before this bug is fixed:
183 /// https://issues.dlang.org/show_bug.cgi?id=11044
184 auto onlyLazy(E)(lazy E value)
185 {
186 	struct Lazy
187 	{
188 		bool empty = false;
189 		@property E front() { assert(!empty); return value; }
190 		void popFront() { assert(!empty); empty = true; }
191 		alias back = front;
192 		alias popBack = popFront;
193 		@property size_t length() { return empty ? 0 : 1; }
194 		E opIndex(size_t i) { assert(!empty); assert(i == 0); return value; }
195 		@property typeof(this) save() { return this; }
196 	}
197 	return Lazy();
198 }
199 
200 static assert(isInputRange!(typeof(onlyLazy(1))));
201 static assert(isForwardRange!(typeof(onlyLazy(1))));
202 static assert(isBidirectionalRange!(typeof(onlyLazy(1))));
203 static assert(isRandomAccessRange!(typeof(onlyLazy(1))));
204 
205 unittest
206 {
207 	import std.algorithm.comparison;
208 	import std.range;
209 
210 	int i;
211 	auto r = onlyLazy(i);
212 	i = 1; assert(equal(r, 1.only));
213 	i = 2; assert(equal(r, 2.only));
214 }
215 
216 // ************************************************************************
217 
218 /// Defer range construction until first empty/front call.
219 auto lazyInitRange(R)(R delegate() constructor)
220 {
221 	bool initialized;
222 	R r = void;
223 
224 	ref R getRange()
225 	{
226 		if (!initialized)
227 		{
228 			r = constructor();
229 			initialized = true;
230 		}
231 		return r;
232 	}
233 
234 	struct LazyRange
235 	{
236 		bool empty() { return getRange().empty; }
237 		auto ref front() { return getRange().front; }
238 		void popFront() { return getRange().popFront; }
239 	}
240 	return LazyRange();
241 }
242 
243 ///
244 unittest
245 {
246 	import std.algorithm.iteration;
247 	import std.range;
248 
249 	int[] todo, done;
250 	chain(
251 		only({ todo = [1, 2, 3]; }),
252 		// eager will fail: todo.map!(n => { done ~= n; }),
253 		lazyInitRange(() => todo.map!(n => { done ~= n; })),
254 	).each!(dg => dg());
255 	assert(done == [1, 2, 3]);
256 }
257 
258 // ************************************************************************
259 
260 /// A faster, random-access version of `cartesianProduct`.
261 auto fastCartesianProduct(R...)(R ranges)
262 {
263 	struct Product
264 	{
265 		size_t start, end;
266 		R ranges;
267 		auto front() { return this[0]; }
268 		void popFront() { start++; }
269 		auto back() { return this[$-1]; }
270 		void popBack() { end--; }
271 		bool empty() const { return start == end; }
272 		size_t length() { return end - start; }
273 		alias opDollar = length;
274 
275 		auto opIndex(size_t index)
276 		{
277 			auto p = start + index;
278 			size_t[R.length] positions;
279 			foreach (i, r; ranges)
280 			{
281 				auto l = r.length;
282 				positions[i] = p % l;
283 				p /= l;
284 			}
285 			assert(p == 0, "Out of bounds");
286 			mixin({
287 				string s;
288 				foreach (i; 0 .. R.length)
289 					s ~= `ranges[` ~ toDec(i) ~ `][positions[` ~ toDec(i) ~ `]], `;
290 				return `return tuple(` ~ s ~ `);`;
291 			}());
292 		}
293 	}
294 	size_t end = 1;
295 	foreach (r; ranges)
296 		end *= r.length;
297 	return Product(0, end, ranges);
298 }
299 
300 unittest
301 {
302 	import std.algorithm.comparison : equal;
303 	assert(fastCartesianProduct().length == 1);
304 	assert(fastCartesianProduct([1, 2, 3]).equal([tuple(1), tuple(2), tuple(3)]));
305 	assert(fastCartesianProduct([1, 2], [3, 4, 5]).equal([
306 		tuple(1, 3), tuple(2, 3),
307 		tuple(1, 4), tuple(2, 4),
308 		tuple(1, 5), tuple(2, 5),
309 	]));
310 }