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 18 import ae.utils.meta : isDebug; 19 20 /// An equivalent of an array range, but which maintains 21 /// a start and end pointer instead of a start pointer 22 /// and length. This allows .popFront to be faster. 23 /// Optionally, omits bounds checking for even more speed. 24 // TODO: Can we make CHECKED implicit, controlled by 25 // -release, like regular arrays? 26 // TODO: Does this actually make a difference in practice? 27 // Run some benchmarks... 28 struct FastArrayRange(T, bool CHECKED=isDebug) 29 { 30 T* ptr, end; 31 32 this(T[] arr) 33 { 34 ptr = arr.ptr; 35 end = ptr + arr.length; 36 } 37 38 @property T front() 39 { 40 static if (CHECKED) 41 assert(!empty); 42 return *ptr; 43 } 44 45 void popFront() 46 { 47 static if (CHECKED) 48 assert(!empty); 49 ptr++; 50 } 51 52 @property bool empty() { return ptr==end; } 53 54 @property ref typeof(this) save() { return this; } 55 56 T opIndex(size_t index) 57 { 58 static if (CHECKED) 59 assert(index < end-ptr); 60 return ptr[index]; 61 } 62 63 T[] opSlice() 64 { 65 return ptrSlice(ptr, end); 66 } 67 68 T[] opSlice(size_t from, size_t to) 69 { 70 static if (CHECKED) 71 assert(from <= to && to <= end-ptr); 72 return ptr[from..to]; 73 } 74 } 75 76 auto fastArrayRange(T)(T[] arr) { return FastArrayRange!T(arr); } 77 78 T[] ptrSlice(T)(T* a, T* b) 79 { 80 return a[0..b-a]; 81 } 82 83 unittest 84 { 85 FastArrayRange!ubyte r; 86 auto x = r.save; 87 } 88 89 // ************************************************************************ 90 91 /// Presents a null-terminated pointer (C-like string) as a range. 92 struct NullTerminated(E) 93 { 94 E* ptr; 95 bool empty() { return !*ptr; } 96 ref E front() { return *ptr; } 97 void popFront() { ptr++; } 98 auto save() { return this; } 99 } 100 auto nullTerminated(E)(E* ptr) 101 { 102 return NullTerminated!E(ptr); 103 } 104 105 unittest 106 { 107 void test(S)(S s) 108 { 109 import std.utf, std.algorithm.comparison; 110 assert(equal(s.byCodeUnit, s.ptr.nullTerminated)); 111 } 112 // String literals are null-terminated 113 test("foo"); 114 test("foo"w); 115 test("foo"d); 116 } 117 118 // ************************************************************************ 119 120 /// Apply a predicate over each consecutive pair. 121 template pairwise(alias pred) 122 { 123 import std.range : zip, dropOne; 124 import std.algorithm.iteration : map; 125 import std.functional : binaryFun; 126 127 auto pairwise(R)(R r) 128 { 129 return zip(r, r.dropOne).map!(pair => binaryFun!pred(pair[0], pair[1])); 130 } 131 } 132 133 /// 134 unittest 135 { 136 import std.algorithm.comparison : equal; 137 assert(equal(pairwise!"a+b"([1, 2, 3]), [3, 5])); 138 assert(equal(pairwise!"b-a"([1, 2, 3]), [1, 1])); 139 } 140 141 // ************************************************************************ 142 143 struct InfiniteIota(T) 144 { 145 T front; 146 enum empty = false; 147 void popFront() { front++; } 148 T opIndex(T offset) { return front + offset; } 149 InfiniteIota save() { return this; } 150 } 151 InfiniteIota!T infiniteIota(T)() { return InfiniteIota!T.init; } 152 153 // ************************************************************************ 154 155 /// Empty range of type E. 156 struct EmptyRange(E) 157 { 158 @property E front() { assert(false); } 159 void popFront() { assert(false); } 160 @property E back() { assert(false); } 161 void popBack() { assert(false); } 162 E opIndex(size_t) { assert(false); } 163 enum empty = true; 164 enum save = typeof(this).init; 165 enum size_t length = 0; 166 } 167 168 /// ditto 169 EmptyRange!E emptyRange(E)() { return EmptyRange!E.init; } 170 171 static assert(isInputRange!(EmptyRange!uint)); 172 static assert(isForwardRange!(EmptyRange!uint)); 173 static assert(isBidirectionalRange!(EmptyRange!uint)); 174 static assert(isRandomAccessRange!(EmptyRange!uint)); 175 176 // ************************************************************************ 177 178 /// Like `only`, but evaluates the argument lazily, i.e. when the 179 /// range's "front" is evaluated. 180 /// DO NOT USE before this bug is fixed: 181 /// https://issues.dlang.org/show_bug.cgi?id=11044 182 auto onlyLazy(E)(lazy E value) 183 { 184 struct Lazy 185 { 186 bool empty = false; 187 @property E front() { assert(!empty); return value; } 188 void popFront() { assert(!empty); empty = true; } 189 alias back = front; 190 alias popBack = popFront; 191 @property size_t length() { return empty ? 0 : 1; } 192 E opIndex(size_t i) { assert(!empty); assert(i == 0); return value; } 193 @property typeof(this) save() { return this; } 194 } 195 return Lazy(); 196 } 197 198 static assert(isInputRange!(typeof(onlyLazy(1)))); 199 static assert(isForwardRange!(typeof(onlyLazy(1)))); 200 static assert(isBidirectionalRange!(typeof(onlyLazy(1)))); 201 static assert(isRandomAccessRange!(typeof(onlyLazy(1)))); 202 203 unittest 204 { 205 import std.algorithm.comparison; 206 import std.range; 207 208 int i; 209 auto r = onlyLazy(i); 210 i = 1; assert(equal(r, 1.only)); 211 i = 2; assert(equal(r, 2.only)); 212 } 213 214 // ************************************************************************ 215 216 /// Defer range construction until first empty/front call. 217 auto lazyInitRange(R)(R delegate() constructor) 218 { 219 bool initialized; 220 R r = void; 221 222 ref R getRange() 223 { 224 if (!initialized) 225 { 226 r = constructor(); 227 initialized = true; 228 } 229 return r; 230 } 231 232 struct LazyRange 233 { 234 bool empty() { return getRange().empty; } 235 auto ref front() { return getRange().front; } 236 void popFront() { return getRange().popFront; } 237 } 238 return LazyRange(); 239 } 240 241 /// 242 unittest 243 { 244 import std.algorithm.iteration; 245 import std.range; 246 247 int[] todo, done; 248 chain( 249 only({ todo = [1, 2, 3]; }), 250 // eager will fail: todo.map!(n => { done ~= n; }), 251 lazyInitRange(() => todo.map!(n => { done ~= n; })), 252 ).each!(dg => dg()); 253 assert(done == [1, 2, 3]); 254 }