1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /** 6 * Iteration algorithms. 7 * 8 * These algorithms wrap other ranges and modify the way, how the original 9 * range is iterated, or the order in which its elements are accessed. 10 * 11 * All algorithms in this module are lazy, they request the next element of the 12 * original range on demand. 13 * 14 * Copyright: Eugene Wissner 2018-2021. 15 * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, 16 * Mozilla Public License, v. 2.0). 17 * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) 18 * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/algorithm/iteration.d, 19 * tanya/algorithm/iteration.d) 20 */ 21 module tanya.algorithm.iteration; 22 23 import std.typecons; 24 import tanya.memory.lifetime; 25 import tanya.meta.trait; 26 import tanya.meta.transform; 27 import tanya.range; 28 29 private struct SingletonByValue(E) 30 { 31 private Nullable!E element; 32 33 @disable this(); 34 35 private this(U)(ref U element) 36 if (is(U == E)) 37 { 38 this.element = move(element); 39 } 40 41 private this(U)(ref U element) 42 if (is(Unqual!U == Nullable!(Unqual!E)) || is(Unqual!U == Nullable!(const E))) 43 { 44 if (!element.isNull) 45 { 46 this.element = element.get; 47 } 48 } 49 50 @property ref inout(E) front() inout 51 in 52 { 53 assert(!empty); 54 } 55 do 56 { 57 return this.element.get; 58 } 59 60 alias back = front; 61 62 void popFront() 63 in 64 { 65 assert(!empty); 66 } 67 do 68 { 69 this.element.nullify(); 70 } 71 72 alias popBack = popFront; 73 74 @property bool empty() const 75 { 76 return this.element.isNull; 77 } 78 79 @property size_t length() const 80 { 81 return !this.element.isNull; 82 } 83 84 auto save() 85 { 86 return SingletonByValue!E(this.element); 87 } 88 89 ref inout(E) opIndex(size_t i) inout 90 in 91 { 92 assert(!empty); 93 assert(i == 0); 94 } 95 do 96 { 97 return this.element.get; 98 } 99 } 100 101 private struct SingletonByRef(E) 102 { 103 private E* element; 104 105 @disable this(); 106 107 private this(return ref E element) @trusted 108 { 109 this.element = &element; 110 } 111 112 @property ref inout(E) front() inout return 113 in 114 { 115 assert(!empty); 116 } 117 do 118 { 119 return *this.element; 120 } 121 122 alias back = front; 123 124 void popFront() 125 in 126 { 127 assert(!empty); 128 } 129 do 130 { 131 this.element = null; 132 } 133 134 alias popBack = popFront; 135 136 @property bool empty() const 137 { 138 return this.element is null; 139 } 140 141 @property size_t length() const 142 { 143 return this.element !is null; 144 } 145 146 auto save() return 147 { 148 return typeof(this)(*this.element); 149 } 150 151 ref inout(E) opIndex(size_t i) inout return 152 in 153 { 154 assert(!empty); 155 assert(i == 0); 156 } 157 do 158 { 159 return *this.element; 160 } 161 } 162 163 /** 164 * Creates a bidirectional and random-access range with the single element 165 * $(D_PARAM element). 166 * 167 * If $(D_PARAM element) is passed by value the resulting range stores it 168 * internally. If $(D_PARAM element) is passed by reference, the resulting 169 * range keeps only a pointer to the element. 170 * 171 * Params: 172 * E = Element type. 173 * element = Element. 174 * 175 * Returns: A range with one element. 176 */ 177 auto singleton(E)(return E element) 178 if (isMutable!E) 179 { 180 return SingletonByValue!E(element); 181 } 182 183 /// ditto 184 auto singleton(E)(return ref E element) 185 { 186 return SingletonByRef!E(element); 187 } 188 189 /// 190 @nogc nothrow pure @safe unittest 191 { 192 auto singleChar = singleton('a'); 193 194 assert(singleChar.length == 1); 195 assert(singleChar.front == 'a'); 196 197 singleChar.popFront(); 198 assert(singleChar.empty); 199 } 200 201 /** 202 * Accumulates all elements of a range using a function. 203 * 204 * $(D_PSYMBOL foldr) takes a function, a bidirectional range and the initial 205 * value. The function takes this initial value and the first element of the 206 * range (in this order), puts them together and returns the result. The return 207 * type of the function should be the same as the type of the initial value. 208 * This is than repeated for all the remaining elements of the range, whereby 209 * the value returned by the passed function is used at the place of the 210 * initial value. 211 * 212 * $(D_PSYMBOL foldr) accumulates from right to left. 213 * 214 * Params: 215 * F = Callable accepting the accumulator and a range element. 216 */ 217 template foldr(F...) 218 if (F.length == 1) 219 { 220 /** 221 * Params: 222 * R = Bidirectional range type. 223 * T = Type of the accumulated value. 224 * range = Bidirectional range. 225 * init = Initial value. 226 * 227 * Returns: Accumulated value. 228 */ 229 auto foldr(R, T)(R range, auto ref T init) 230 if (isBidirectionalRange!R) 231 { 232 if (range.empty) 233 { 234 return init; 235 } 236 else 237 { 238 auto acc = F[0](init, getAndPopBack(range)); 239 return foldr(range, acc); 240 } 241 } 242 } 243 244 /// 245 @nogc nothrow pure @safe unittest 246 { 247 int[3] range = [1, 2, 3]; 248 int[3] output; 249 const int[3] expected = [3, 2, 1]; 250 251 alias f = (acc, x) { 252 acc.front = x; 253 acc.popFront; 254 return acc; 255 }; 256 const actual = foldr!f(range[], output[]); 257 258 assert(output[] == expected[]); 259 }