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 }