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 }