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