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 }