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