1 #!/usr/bin/env rdmd-unittest-module
2 
3 /** Lazy Substitution Algorithms.
4 
5     See also: http://forum.dlang.org/post/pymxzazxximtgixzbnpq@forum.dlang.org
6  */
7 module substitution;
8 
9 import std.range : isInputRange, ElementType;
10 import std.typecons : Unqual, Tuple;
11 import std.traits : isExpressionTuple;
12 import std.algorithm : startsWith;
13 
14 import traits_ex : hasEvenLength, haveCommonType;
15 
16 import std.stdio;
17 
18 version(unittest)
19 {
20     import std.algorithm.comparison : equal;
21 }
22 
23 /** Substitute in parallel all elements in $(D r) which equal (according to $(D
24     pred)) $(D ss[2*n]) with $(D ss[2*n + 1]) for $(D n) = 0, 1, 2, ....
25 */
26 auto substitute(alias pred = (a, b) => a == b, R, Ss...)(R r, Ss ss)
27     if (isInputRange!(Unqual!R) &&
28         Ss.length >= 2 &&
29         hasEvenLength!Ss &&
30         haveCommonType!(ElementType!R, Ss))
31 {
32     import std.algorithm.iteration : map;
33     import std.functional : binaryFun;
34     enum n = Ss.length / 2;
35     auto replaceElement(E)(E e)
36     {
37         import range_ex : iota;
38         foreach (const i; iota!(0, n))
39         {
40             if (binaryFun!pred(e, ss[2*i]))
41                 return ss[2*i + 1];
42         }
43         return e;
44     }
45     return r.map!(a => replaceElement(a));
46 }
47 
48 ///
49 @safe pure unittest
50 {
51     import std.conv : to;
52     import std.algorithm : map;
53     auto x = `42`.substitute('2', '3')
54                  .substitute('3', '1');
55     static assert(is(ElementType!(typeof(x)) == dchar));
56     assert(equal(x, `41`));
57 }
58 
59 ///
60 @safe pure unittest
61 {
62     assert(`do_it`.substitute('_', ' ')
63                   .equal(`do it`));
64     int[3] x = [1, 2, 3];
65     auto y = x[].substitute(1, 0.1)
66                 .substitute(0.1, 0.2);
67     static assert(is(typeof(y.front) == double));
68     assert(y.equal([0.2, 2, 3]));
69     assert(`do_it`.substitute('_', ' ',
70                               'd', 'g',
71                               'i', 't',
72                               't', 'o')
73                   .equal(`go to`));
74     import std.range : retro;
75     assert(equal([1, 2, 3].substitute(1, 0.1)
76                           .retro,
77                  [3, 2, 0.1]));
78 }
79 
80 /** Substitute in parallel all elements in $(D r) which equal (according to $(D
81     pred)) $(D ss[2*n]) with $(D ss[2*n + 1]) for $(D n) = 0, 1, 2, ....
82 
83     Because $(D ss) are known at compile time, time-complexity for each element
84     substitution is O(1).
85 */
86 template substitute(ss...)
87     if (isExpressionTuple!ss &&
88         ss.length >= 2 &&
89         hasEvenLength!ss)
90 {
91     auto substitute(R)(R r)
92         if (isInputRange!(Unqual!R) &&
93             haveCommonType!(ElementType!R, ss))
94     {
95         import std.algorithm.iteration : map;
96         enum n = ss.length / 2;
97         auto replaceElement(E)(E e)
98         {
99             import range_ex : iota;
100             switch (e)
101             {
102                 foreach (const i; iota!(0, n))
103                 {
104                 case ss[2*i]: return ss[2*i + 1];
105                 }
106             default: return e;
107             }
108         }
109         return r.map!(a => replaceElement(a));
110     }
111 }
112 
113 ///
114 @safe pure unittest
115 {
116     assert(`do_it`.substitute!('_', ' ')
117                   .equal(`do it`));
118     int[3] x = [1, 2, 3];
119     auto y = x[].substitute!(1, 0.1);
120     assert(y.equal([0.1, 2, 3]));
121     static assert(is(typeof(y.front) == double));
122     assert(`do_it`.substitute!('_', ' ',
123                                'd', 'g',
124                                'i', 't',
125                                't', 'o')
126                   .equal(`go to`));
127     import std.range : retro;
128     assert(equal([1, 2, 3].substitute!(1, 0.1)
129                           .retro,
130                  [3, 2, 0.1]));
131 }
132 
133 /** Helper range for subsequence overload of $(D substitute).
134  */
135 private auto substituteSplitter(alias pred = `a == b`, R, Rs...)(R haystack, Rs needles)
136     if (Rs.length >= 1 &&
137         is(typeof(startsWith!pred(haystack, needles))))
138 {
139     static struct Result
140     {
141         alias Hit = size_t; // 0 iff no hit, otherwise hit in needles[index-1]
142         alias E = Tuple!(R, Hit);
143         this(R haystack, Rs needles)
144         {
145             this._rest = haystack;
146             this._needles = needles;
147             popFront();
148         }
149 
150         @property auto ref front()
151         {
152             import std.range : empty;
153             return !_skip.empty ? E(_skip, 0) : E(_hit, _hitNr);
154         }
155 
156         import std.range : isInfinite;
157         static if (isInfinite!R)
158             enum empty = false; // propagate infiniteness
159         else
160             @property bool empty() const
161             {
162                 import std.range : empty;
163                 return _skip.empty && _hit.empty && _rest.empty;
164             }
165 
166         void popFront()
167         {
168             import std.range : empty;
169             if (!_skip.empty)
170             {
171                 _skip = R.init; // jump over _skip
172             }
173             else
174             {
175                 import std.algorithm.searching : find;
176 
177                 static if (_needles.length >= 2) // if variadic version
178                 {
179                     auto match = _rest.find!pred(_needles);
180                     auto hitValue = match[0];
181                     _hitNr = match[1];
182                 }
183                 else
184                 {
185                     auto match = _rest.find!pred(_needles);
186                     auto hitValue = match;
187                     _hitNr = !match.empty ? 1 : 0;
188                 }
189 
190                 if (_hitNr == 0) // no more hits
191                 {
192                     _skip = _rest;
193                     _hit = R.init;
194                     _rest = R.init;
195                 }
196                 else
197                 {
198                     import std.range : isSomeString;
199                     static if (isSomeString!R)
200                     {
201                         size_t hitLength = size_t.max;
202                         final switch (_hitNr - 1)
203                         {
204                             foreach (const i, Ri; Rs)
205                             {
206                             case i: hitLength = _needles[i].length; break;
207                             }
208                         }
209                         assert(hitLength != size_t.max); // not needed if match returned bounded!int
210 
211                         if (_rest.ptr == hitValue.ptr) // match at start of _rest
212                         {
213                             _hit = hitValue[0 .. hitLength];
214                             _rest = hitValue[hitLength .. $];
215                         }
216                         else
217                         {
218                             _skip = _rest[0 .. hitValue.ptr - _rest.ptr];
219                             _hit = hitValue[0 .. hitLength];
220                             _rest = hitValue[hitLength .. $];
221                         }
222                     }
223                     else
224                     {
225                         static assert(false, `Handle R without slicing ` ~ R.stringof);
226                     }
227                 }
228             }
229         }
230     private:
231         R _rest;
232         Rs _needles;
233         R _skip; // skip before next hit
234         R _hit; // most recent (last) hit if any
235         size_t _hitNr; // hit number: 0 means no hit, otherwise index+1 to needles that matched
236     }
237     return Result(haystack, needles);
238 }
239 
240 @safe pure unittest
241 {
242     auto h = `alpha.beta.gamma`;
243     auto fs = h.substituteSplitter(`alpha`, `beta`, `gamma`);
244     alias FS = typeof(fs);
245     alias E = ElementType!FS;
246     static assert(is(E == Tuple!(string, ulong)));
247     assert(equal(fs, [E(`alpha`, 1),
248                       E(`.`, 0),
249                       E(`beta`, 2),
250                       E(`.`, 0),
251                       E(`gamma`, 3)]));
252 }
253 
254 @safe pure unittest
255 {
256     auto h = `.alpha.beta.gamma`;
257     auto fs = h.substituteSplitter(`alpha`, `beta`, `gamma`);
258     alias FS = typeof(fs);
259     alias E = ElementType!FS;
260     static assert(is(E == Tuple!(string, ulong)));
261     assert(equal(fs, [E(`.`, 0),
262                       E(`alpha`, 1),
263                       E(`.`, 0),
264                       E(`beta`, 2),
265                       E(`.`, 0),
266                       E(`gamma`, 3)]));
267 }
268 
269 @safe pure unittest
270 {
271     auto h = `alpha.beta.gamma.`;
272     auto fs = h.substituteSplitter(`alpha`, `beta`, `gamma`);
273     alias FS = typeof(fs);
274     alias E = ElementType!FS;
275     static assert(is(E == Tuple!(string, ulong)));
276     assert(equal(fs, [E(`alpha`, 1),
277                       E(`.`, 0),
278                       E(`beta`, 2),
279                       E(`.`, 0),
280                       E(`gamma`, 3),
281                       E(`.`, 0)]));
282 }
283 
284 @safe pure unittest
285 {
286     auto h = `alpha.alpha.alpha.`;
287     auto fs = h.substituteSplitter(`alpha`);
288     alias FS = typeof(fs);
289     alias E = ElementType!FS;
290     static assert(is(E == Tuple!(string, ulong)));
291     assert(equal(fs, [E(`alpha`, 1),
292                       E(`.`, 0),
293                       E(`alpha`, 1),
294                       E(`.`, 0),
295                       E(`alpha`, 1),
296                       E(`.`, 0)]));
297 }
298 
299 template Stride(size_t stride, size_t offset, Args...)
300     if (stride > 0)
301 {
302     import std.meta : AliasSeq;
303     static if (offset >= Args.length)
304     {
305         alias Stride = AliasSeq!();
306     }
307     else static if (stride >= Args.length)
308     {
309         alias Stride = AliasSeq!(Args[offset]);
310     }
311     else
312     {
313         alias Stride = AliasSeq!(Args[offset],
314                                  Stride!(stride, offset, Args[stride .. $]));
315     }
316 }
317 
318 /** Substitute in parallel all subsequences in $(D r) which equal (according to
319     $(D pred)) $(D ss[2*n]) with $(D ss[2*n + 1]) for $(D n) = 0, 1, 2, ....
320 */
321 auto substitute(alias pred = (a, b) => a == b, R, Ss...)(R r, Ss ss)
322     if (isInputRange!(Unqual!R) &&
323         Ss.length >= 2 &&
324         hasEvenLength!Ss &&
325         haveCommonType!(ElementType!R,
326                         ElementType!(Ss[0])))
327 {
328     import std.algorithm.iteration : map;
329     import std.functional : binaryFun;
330 
331     enum n = Ss.length / 2;
332 
333     auto replaceElement(E)(E e)
334     {
335         auto value = e[0];
336         const hitNr = e[1];
337         import range_ex : iota;
338         if (hitNr == 0) // not hit
339         {
340             return value;
341         }
342         else
343         {
344             foreach (const i; iota!(0, n))
345             {
346                 if (hitNr == i + 1)
347                     return ss[2*i + 1]; // replacement
348             }
349             assert(false);
350         }
351     }
352 
353     // extract inputs
354     alias Ins = Stride!(2, 0, Ss);
355     Ins ins;
356     import range_ex : iota;
357     foreach (const i; iota!(0, n))
358     {
359         ins[i] = ss[2*i];
360     }
361 
362     import std.algorithm.iteration : map, joiner;
363     return r.substituteSplitter!pred(ins)
364             .map!(a => replaceElement(a))
365             .joiner;
366 }
367 
368 @safe pure unittest
369 {
370     assert(`do_it now, sir`.substitute(`_`, ` `,
371                                        `d`, `g`,
372                                        `i`, `t`,
373                                        `t`, `o`,
374                                        `now`, `work`,
375                                        `sir`, `please`)
376                   .equal(`go to work, please`));
377 }
378 
379 @safe pure unittest
380 {
381     assert((`abcDe`.substitute(`a`, `AA`,
382                                `b`, `DD`)
383                    .substitute('A', 'y',
384                                'D', 'x',
385                                'e', '1'))
386            .equal(`yyxxcx1`));
387 }