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 }