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 debug(ae_unittest) 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 debug(ae_unittest) 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 debug(ae_unittest) 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 debug(ae_unittest) 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 debug(ae_unittest) 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 debug(ae_unittest) 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 debug(ae_unittest) 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;