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 }