From 1c27510b3a520f9f9cfc8c551d04b30468794639 Mon Sep 17 00:00:00 2001 From: Sebastian Wilzbach Date: Tue, 11 Jul 2017 19:36:50 +0200 Subject: [PATCH 1/4] Make `empty` of std.range.take `const` if possible --- std/range/package.d | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/std/range/package.d b/std/range/package.d index e930e16dc..5d62c2b19 100644 --- a/std/range/package.d +++ b/std/range/package.d @@ -2010,6 +2010,13 @@ if (isInputRange!(Unqual!Range) && return _maxAvailable == 0 || source.empty; } + static if (is(typeof((cast(const R) source).empty))) + /// ditto + @property bool empty() const + { + return _maxAvailable == 0 || source.empty; + } + /// ditto @property auto ref front() { From 41d158f4c71f973d7b1dc8e5744a0bdc0580199d Mon Sep 17 00:00:00 2001 From: Sebastian Wilzbach Date: Tue, 11 Jul 2017 19:37:35 +0200 Subject: [PATCH 2/4] Add substitute for lazy replacements --- .../std-algorithm-iteration-substitute.dd | 49 ++ std/algorithm/iteration.d | 609 ++++++++++++++++++ std/algorithm/package.d | 1 + std/array.d | 8 +- std/string.d | 15 +- 5 files changed, 677 insertions(+), 5 deletions(-) create mode 100644 changelog/std-algorithm-iteration-substitute.dd diff --git a/changelog/std-algorithm-iteration-substitute.dd b/changelog/std-algorithm-iteration-substitute.dd new file mode 100644 index 000000000..20557c03e --- /dev/null +++ b/changelog/std-algorithm-iteration-substitute.dd @@ -0,0 +1,49 @@ +`std.algorithm.searching.substitute` was added + +$(REF substitute, std,algorithm,searching) yields a lazy range with +all occurrences of $(D substs) in $(R r) replaced with their substitution: + +--- +import std.algorithm.comparison : equal; +import std.algorithm.searching : substitute; + +// substitute single elements +assert("do_it".substitute('_', ' ').equal("do it")); + +// substitute multiple, single elements +assert("do_it".substitute('_', ' ', + 'd', 'g', + 'i', 't', + 't', 'o') + .equal("go to")); + +// substitute subranges +assert("do_it".substitute("_", " ", + "do", "done") + .equal("done it")); + +// substitution works for any ElementType +int[] x = [1, 2, 3]; +auto y = x.substitute(1, 0.1); +assert(y.equal([0.1, 2, 3])); +static assert(is(typeof(y.front) == double)); +--- + +If the substitution parameters are known at compile-time, the faster +template overload can be used: + +--- +import std.algorithm.comparison : equal; +import std.algorithm.searching : substitute; + +// substitute subranges of a range +assert("apple_tree".substitute!("apple", "banana", + "tree", "shrub").equal("banana_shrub")); + +// substitute elements in a range +assert("apple_tree".substitute!('a', 'b', + 't', 'f').equal("bpple_free")); + +// substitute values +assert('a'.substitute!('a', 'b', 't', 'f') == 'b'); +--- diff --git a/std/algorithm/iteration.d b/std/algorithm/iteration.d index 275fd91cb..23d743c1d 100644 --- a/std/algorithm/iteration.d +++ b/std/algorithm/iteration.d @@ -47,6 +47,8 @@ $(T2 reduce, This is the old implementation of `fold`.) $(T2 splitter, Lazily splits a range by a separator.) +$(T2 substitute, + $(D [1, 2].substitute(1, 0.1)) returns $(D [0.1, 2]).) $(T2 sum, Same as `fold`, but specialized for accurate summation.) $(T2 uniq, @@ -4687,6 +4689,613 @@ if (isSomeChar!C) } } +// substitute +/** +Returns a range with all occurrences of `substs` in `r`. +replaced with their substitution. + +Single value replacements (`'ö'.substitute!('ä', 'a', 'ö', 'o', 'ü', 'u)`) are +supported as well and in $(BIGOH 1). + +Params: + r = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives) + value = a single value which can be substituted in $(BIGOH 1) + substs = a set of replacements/substitutions + +Returns: a range with the substitutions replaced. + +See_Also: +$(REF replace, std, array) for an eager replace algorithm or +$(REF translate, std, string), and $(REF tr, std, string) +for string algorithms with translation tables. +*/ +template substitute(substs...) +if (substs.length >= 2 && isExpressions!substs) +{ + import std.range.primitives : ElementType; + import std.traits : CommonType; + + static assert(!(substs.length & 1), "The number of substitution parameters must be even"); + + /** + Substitute single values with compile-time substitution mappings. + Complexity: $(BIGOH 1) due to D's $(D switch) guaranteeing $(BIGOH 1); + */ + auto substitute(Value)(Value value) + if (!isInputRange!Value && !is(CommonType!(Value, typeof(substs[0])) == void)) + { + switch (value) + { + static foreach (i; 0 .. substs.length / 2) + case substs[2 * i]: + return substs[2 * i + 1]; + + default: return value; + } + } + + /// Substitute single range elements with compile-time substitution mappings + auto substitute(R)(R r) + if (isInputRange!R && !is(CommonType!(ElementType!R, typeof(substs[0])) == void)) + { + return r.map!(a => substitute(a)); + } + + /// Substitute subranges with compile-time substitution mappings + auto substitute(R)(R r) + if (isInputRange!R && !is(CommonType!(ElementType!R, ElementType!(typeof(substs[0]))) == void)) + { + // not implemented yet, fallback to runtime variant for now + return .substitute(r, substs); + } +} + +// In same combinations substitute needs to calculate the auto-decoded length +// of its needles +private template needsManualAutodecoding(Range, Needles...) +{ + import std.meta : anySatisfy; + /* iff + - the needles needs auto-decoding, but the incoming range doesn't + - (vice versa) if the incoming range needs auto-decoding, but the needle don't + - both (range, needle) need auto-decoding and don't share the same common type + */ + enum needlesAreNarrow = anySatisfy!(isNarrowString, Needles); + enum sourceIsNarrow = isNarrowString!Range; + enum needsManualAutodecoding = ( sourceIsNarrow && !needlesAreNarrow) || + (!sourceIsNarrow && needlesAreNarrow) || + ((sourceIsNarrow && needlesAreNarrow) && + is(CommonType!(Range, Needles) == void)); +} + +@safe nothrow @nogc pure unittest +{ + import std.meta : AliasSeq; // used for better clarity + + static assert(!needsManualAutodecoding!(string, AliasSeq!(string, string))); + static assert(!needsManualAutodecoding!(wstring, AliasSeq!(wstring, wstring))); + static assert(!needsManualAutodecoding!(dstring, AliasSeq!(dstring, dstring))); + + static assert(needsManualAutodecoding!(string, AliasSeq!(wstring, string))); + static assert(needsManualAutodecoding!(wstring, AliasSeq!(string, string))); + + static assert(needsManualAutodecoding!(dstring, AliasSeq!(string, string))); + static assert(needsManualAutodecoding!(dstring, AliasSeq!(string, wstring))); + + static assert(needsManualAutodecoding!(string, AliasSeq!(dstring, string))); + static assert(needsManualAutodecoding!(string, AliasSeq!(wstring, string))); +} + +/// ditto +auto substitute(alias pred = (a, b) => a == b, R, Substs...)(R r, Substs substs) +if (isInputRange!R && Substs.length >= 2 && !is(CommonType!(Substs) == void)) +{ + import std.range.primitives : ElementType; + import std.meta : allSatisfy; + import std.traits : CommonType; + + static assert(!(Substs.length & 1), "The number of substitution parameters must be even"); + + enum n = Substs.length / 2; + + // Substitute individual elements + static if (!is(CommonType!(ElementType!R, Substs) == void)) + { + import std.functional : binaryFun; + + // Imitate a value closure to be @nogc + static struct ReplaceElement + { + private Substs substs; + + this(Substs substs) + { + this.substs = substs; + } + + auto opCall(E)(E e) + { + static foreach (i; 0 .. n) + if (binaryFun!pred(e, substs[2 * i])) + return substs[2 * i + 1]; + + return e; + } + } + auto er = ReplaceElement(substs); + return r.map!er; + } + // Substitute subranges + else static if (!is(CommonType!(ElementType!R, ElementType!(Substs[0])) == void) && + allSatisfy!(isForwardRange, Substs)) + { + import std.range : choose, take; + import std.meta : Stride; + + auto replaceElement(E)(E e) + { + alias ReturnA = typeof(e[0]); + alias ReturnB = typeof(substs[0 .. 1].take(1)); + + // 1-based index + const auto hitNr = e[1]; + switch (hitNr) + { + // no hit + case 0: + // use choose trick for non-common range + static if (is(CommonType!(ReturnA, ReturnB) == void)) + return choose(1, e[0], ReturnB.init); + else + return e[0]; + + // all replacements + static foreach (i; 0 .. n) + case i + 1: + // use choose trick for non-common ranges + static if (is(CommonType!(ReturnA, ReturnB) == void)) + return choose(0, e[0], substs[2 * i + 1].take(size_t.max)); + else + return substs[2 * i + 1].take(size_t.max); + default: + assert(0, "hitNr should always be found."); + } + } + + alias Ins = Stride!(2, Substs); + + static struct SubstituteSplitter + { + import std.range : drop; + import std.typecons : Tuple; + + private + { + typeof(R.init.drop(0)) rest; + Ins needles; + + typeof(R.init.take(0)) skip; // skip before next hit + alias Hit = size_t; // 0 iff no hit, otherwise hit in needles[index-1] + alias E = Tuple!(typeof(skip), Hit); + Hit hitNr; // hit number: 0 means no hit, otherwise index+1 to needles that matched + bool hasHit; // is there a replacement hit which should be printed? + + enum needsManualAutodecoding = .needsManualAutodecoding!(typeof(rest), Ins); + + // calculating the needle length for narrow strings might be expensive -> cache it + static if (needsManualAutodecoding) + ptrdiff_t[n] needleLengths = -1; + } + + this(R haystack, Ins needles) + { + hasHit = !haystack.empty; + this.rest = haystack.drop(0); + this.needles = needles; + popFront; + static if (hasNested!(typeof(skip))) + skip = rest.take(0); + } + + /* If `skip` is non-empty, it's returned as (skip, 0) tuple + otherwise a similar (, hitNr) tuple is returned. + `replaceElement` maps based on the second item (`hitNr`). + */ + @property auto ref front() + { + assert(!empty, "Attempting to fetch the front of an empty substitute."); + return !skip.empty ? E(skip, 0) : E(typeof(skip).init, hitNr); + } + + static if (isInfinite!R) + enum empty = false; // propagate infiniteness + else + @property bool empty() + { + return skip.empty && !hasHit; + } + + /* If currently in a skipping phase => reset. + Otherwise try to find the next occurrence of `needles` + If valid match + - if there are elements before the match, set skip with these elements + (on the next popFront, the range will be in the skip state once) + - `rest`: advance to the end of the match + - set hasHit + Otherwise skip to the end + */ + void popFront() + { + assert(!empty, "Attempting to popFront an empty substitute."); + if (!skip.empty) + { + skip = typeof(skip).init; // jump over skip + } + else + { + import std.algorithm.searching : countUntil, find; + + auto match = rest.find!pred(needles); + + static if (needles.length >= 2) // variadic version of find (returns a tuple) + { + // find with variadic needles returns a (range, needleNr) tuple + // needleNr is a 1-based index + auto hitValue = match[0]; + hitNr = match[1]; + } + else + { + // find with one needle returns the range + auto hitValue = needles[0]; + hitNr = match.empty ? 0 : 1; + } + + if (hitNr == 0) // no more hits + { + skip = rest.take(size_t.max); + hasHit = false; + rest = typeof(rest).init; + } + else + { + auto hitLength = size_t.max; + SWITCH: switch (hitNr - 1) + { + static foreach (i; 0 .. n) + { + case i: + static if (needsManualAutodecoding) + { + import std.utf : codeLength; + + // cache calculated needle length + if (needleLengths[i] != -1) + hitLength = needleLengths[i]; + else + hitLength = needleLengths[i] = codeLength!dchar(needles[i]); + } + else + { + hitLength = needles[i].length; + } + break SWITCH; + } + default: + assert(0, "hitNr should always be found"); + } + + const pos = rest.countUntil(hitValue); + if (pos > 0) // match not at start of rest + skip = rest.take(pos); + + hasHit = true; + + // iff the source range and the substitutions are narrow strings, + // we can avoid calling the auto-decoding `popFront` (via drop) + static if (isNarrowString!(typeof(hitValue)) && !needsManualAutodecoding) + rest = hitValue[hitLength .. $]; + else + rest = hitValue.drop(hitLength); + } + } + } + } + + // extract inputs + Ins ins; + static foreach (i; 0 .. n) + ins[i] = substs[2 * i]; + + return SubstituteSplitter(r, ins) + .map!(a => replaceElement(a)) + .joiner; + } + else + { + static assert(0, "The substitutions must either substitute a single element or a save-able subrange."); + } +} + +/// +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + // substitute single elements + assert("do_it".substitute('_', ' ').equal("do it")); + + // substitute multiple, single elements + assert("do_it".substitute('_', ' ', + 'd', 'g', + 'i', 't', + 't', 'o') + .equal("go to")); + + // substitute subranges + assert("do_it".substitute("_", " ", + "do", "done") + .equal("done it")); + + // substitution works for any ElementType + int[] x = [1, 2, 3]; + auto y = x.substitute(1, 0.1); + assert(y.equal([0.1, 2, 3])); + static assert(is(typeof(y.front) == double)); + + import std.range : retro; + assert([1, 2, 3].substitute(1, 0.1).retro.equal([3, 2, 0.1])); +} + +/// Use faster compile-time overload +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + // substitute subranges of a range + assert("apple_tree".substitute!("apple", "banana", + "tree", "shrub").equal("banana_shrub")); + + // substitute subranges of a range + assert("apple_tree".substitute!('a', 'b', + 't', 'f').equal("bpple_free")); + + // substitute values + assert('a'.substitute!('a', 'b', 't', 'f') == 'b'); +} + +/// Multiple substitutes +@safe pure unittest +{ + import std.algorithm.comparison : equal; + import std.range.primitives : ElementType; + + int[3] x = [1, 2, 3]; + auto y = x[].substitute(1, 0.1) + .substitute(0.1, 0.2); + static assert(is(typeof(y.front) == double)); + assert(y.equal([0.2, 2, 3])); + + auto z = "42".substitute('2', '3') + .substitute('3', '1'); + static assert(is(ElementType!(typeof(z)) == dchar)); + assert(equal(z, "41")); +} + +// Test the first example with Compile-Time overloads +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + // substitute single elements + assert("do_it".substitute!('_', ' ').equal("do it")); + + // substitute multiple, single elements + assert("do_it".substitute!('_', ' ', + 'd', 'g', + 'i', 't', + 't', 'o') + .equal(`go to`)); + + // substitute subranges + assert("do_it".substitute!("_", " ", + "do", "done") + .equal("done it")); + + // substitution works for any ElementType + int[3] x = [1, 2, 3]; + auto y = x[].substitute!(1, 0.1); + assert(y.equal([0.1, 2, 3])); + static assert(is(typeof(y.front) == double)); + + import std.range : retro; + assert([1, 2, 3].substitute!(1, 0.1).retro.equal([3, 2, 0.1])); +} + +// test infinite ranges +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.range : cycle, take; + + int[] x = [1, 2, 3]; + assert(x.cycle.substitute!(1, 0.1).take(4).equal([0.1, 2, 3, 0.1])); + assert(x.cycle.substitute(1, 0.1).take(4).equal([0.1, 2, 3, 0.1])); +} + +// test infinite ranges +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges; + + foreach (R; AllDummyRanges) + { + assert(R.init + .substitute!(2, 22, 3, 33, 5, 55, 9, 99) + .equal([1, 22, 33, 4, 55, 6, 7, 8, 99, 10])); + + assert(R.init + .substitute(2, 22, 3, 33, 5, 55, 9, 99) + .equal([1, 22, 33, 4, 55, 6, 7, 8, 99, 10])); + } +} + +// test multiple replacements +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert("alpha.beta.gamma" + .substitute("alpha", "1", + "gamma", "3", + "beta", "2").equal("1.2.3")); + + assert("alpha.beta.gamma." + .substitute("alpha", "1", + "gamma", "3", + "beta", "2").equal("1.2.3.")); + + assert("beta.beta.beta" + .substitute("alpha", "1", + "gamma", "3", + "beta", "2").equal("2.2.2")); + + assert("alpha.alpha.alpha" + .substitute("alpha", "1", + "gamma", "3", + "beta", "2").equal("1.1.1")); +} + +// test combination of subrange + element replacement +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert(("abcDe".substitute("a", "AA", + "b", "DD") + .substitute('A', 'y', + 'D', 'x', + 'e', '1')) + .equal("yyxxcx1")); +} + +// test const + immutable storage groups +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + auto xyz_abc(T)(T value) + { + immutable a = "a"; + const b = "b"; + auto c = "c"; + return value.substitute!("x", a, + "y", b, + "z", c); + } + assert(xyz_abc("_x").equal("_a")); + assert(xyz_abc(".y.").equal(".b.")); + assert(xyz_abc("z").equal("c")); + assert(xyz_abc("w").equal("w")); +} + +// test with narrow strings (auto-decoding) and subranges +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert("äöü€".substitute("ä", "b", "ü", "u").equal("böu€")); + assert("äöü€".substitute!("ä", "b", "ü", "u").equal("böu€")); + assert("ä...öü€".substitute("ä", "b", "ü", "u").equal("b...öu€")); + + auto expected = "emoticons😄😅.😇😈Rock"; + assert("emoticons😄😅😆😇😈rock" + .substitute("r", "R", "😆", ".").equal(expected)); + assert("emoticons😄😅😆😇😈rock" + .substitute!("r", "R", "😆", ".").equal(expected)); +} + +// test with narrow strings (auto-decoding) and single elements +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert("äöü€".substitute('ä', 'b', 'ü', 'u').equal("böu€")); + assert("äöü€".substitute!('ä', 'b', 'ü', 'u').equal("böu€")); + + auto expected = "emoticons😄😅.😇😈Rock"; + assert("emoticons😄😅😆😇😈rock" + .substitute('r', 'R', '😆', '.').equal(expected)); + assert("emoticons😄😅😆😇😈rock" + .substitute!('r', 'R', '😆', '.').equal(expected)); +} + +// test auto-decoding {n,w,d} strings X {n,w,d} strings +@safe pure unittest +{ + import std.algorithm.comparison : equal; + + assert("ääöü€".substitute("ä", "b", "ü", "u").equal("bböu€")); + assert("ääöü€".substitute("ä"w, "b"w, "ü"w, "u"w).equal("bböu€")); + assert("ääöü€".substitute("ä"d, "b"d, "ü"d, "u"d).equal("bböu€")); + + assert("ääöü€"w.substitute("ä", "b", "ü", "u").equal("bböu€")); + assert("ääöü€"w.substitute("ä"w, "b"w, "ü"w, "u"w).equal("bböu€")); + assert("ääöü€"w.substitute("ä"d, "b"d, "ü"d, "u"d).equal("bböu€")); + + assert("ääöü€"d.substitute("ä", "b", "ü", "u").equal("bböu€")); + assert("ääöü€"d.substitute("ä"w, "b"w, "ü"w, "u"w).equal("bböu€")); + assert("ääöü€"d.substitute("ä"d, "b"d, "ü"d, "u"d).equal("bböu€")); + + // auto-decoding is done before by a different range + assert("ääöü€".filter!(a => true).substitute("ä", "b", "ü", "u").equal("bböu€")); + assert("ääöü€".filter!(a => true).substitute("ä"w, "b"w, "ü"w, "u"w).equal("bböu€")); + assert("ääöü€".filter!(a => true).substitute("ä"d, "b"d, "ü"d, "u"d).equal("bböu€")); +} + +// test repeated replacement +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + + assert([1, 2, 3, 1, 1, 2].substitute(1, 0).equal([0, 2, 3, 0, 0, 2])); + assert([1, 2, 3, 1, 1, 2].substitute!(1, 0).equal([0, 2, 3, 0, 0, 2])); + assert([1, 2, 3, 1, 1, 2].substitute(1, 2, 2, 9).equal([2, 9, 3, 2, 2, 9])); +} + +// test @nogc for single element replacements +@safe @nogc unittest +{ + import std.algorithm.comparison : equal; + + static immutable arr = [1, 2, 3, 1, 1, 2]; + static immutable expected = [0, 2, 3, 0, 0, 2]; + + assert(arr.substitute!(1, 0).equal(expected)); + assert(arr.substitute(1, 0).equal(expected)); +} + +// test different range types +@safe pure nothrow unittest +{ + import std.algorithm.comparison : equal; + import std.internal.test.dummyrange : AllDummyRanges; + + static foreach (DummyType; AllDummyRanges) + {{ + DummyType dummyRange; + + // single substitution + dummyRange.substitute (2, 22).equal([1, 22, 3, 4, 5, 6, 7, 8, 9, 10]); + dummyRange.substitute!(2, 22).equal([1, 22, 3, 4, 5, 6, 7, 8, 9, 10]); + + // multiple substitution + dummyRange.substitute (2, 22, 5, 55, 7, 77).equal([1, 22, 3, 4, 55, 6, 77, 8, 9, 10]); + dummyRange.substitute!(2, 22, 5, 55, 7, 77).equal([1, 22, 3, 4, 55, 6, 77, 8, 9, 10]); + }} +} + // sum /** Sums elements of `r`, which must be a finite diff --git a/std/algorithm/package.d b/std/algorithm/package.d index 1ca4eeefc..d96262253 100644 --- a/std/algorithm/package.d +++ b/std/algorithm/package.d @@ -82,6 +82,7 @@ $(TR $(SUBREF iteration, permutations) $(SUBREF iteration, reduce) $(SUBREF iteration, splitter) + $(SUBREF iteration, substitute) $(SUBREF iteration, sum) $(SUBREF iteration, uniq) ) diff --git a/std/array.d b/std/array.d index 5419d460e..946c12819 100644 --- a/std/array.d +++ b/std/array.d @@ -2116,7 +2116,7 @@ if (isInputRange!RoR && is found. See_Also: - $(REF map, std,algorithm,iteration) which can act as a lazy replace + $(REF substitute, std,algorithm,iteration) for a lazy replace. +/ E[] replace(E, R1, R2)(E[] subject, R1 from, R2 to) if (isDynamicArray!(E[]) && isForwardRange!R1 && isForwardRange!R2 @@ -2289,6 +2289,9 @@ if (isOutputRange!(Sink, E) && isDynamicArray!(E[]) Returns: A new array without changing the contents of `subject`. + + See_Also: + $(REF substitute, std,algorithm,iteration) for a lazy replace. +/ T[] replace(T, Range)(T[] subject, size_t from, size_t to, Range stuff) if (isInputRange!Range && @@ -2827,6 +2830,9 @@ if (isDynamicArray!(E[]) && Returns: A new array that is `s` with `slice` replaced by `replacement[]`. + + See_Also: + $(REF substitute, std,algorithm,iteration) for a lazy replace. +/ inout(T)[] replaceSlice(T)(inout(T)[] s, in T[] slice, in T[] replacement) in diff --git a/std/string.d b/std/string.d index 3c2397601..1c334a1d0 100644 --- a/std/string.d +++ b/std/string.d @@ -4723,8 +4723,9 @@ unittest A copy with the changes is returned. See_Also: - $(LREF tr) - $(REF replace, std,array) + $(LREF tr), + $(REF replace, std,array), + $(REF substitute, std,algorithm,iteration) Params: str = The original string. @@ -4993,8 +4994,9 @@ private void translateImpl(C1, T, C2, Buffer)(C1[] str, unit greater than $(D 127) which will cause UTF validation issues. See_Also: - $(LREF tr) - $(REF replace, std,array) + $(LREF tr), + $(REF replace, std,array), + $(REF substitute, std,algorithm,iteration) Params: str = The original string. @@ -5633,6 +5635,11 @@ if (isSomeString!S) (e.g. $(D "a-d") is synonymous with $(D "abcd").) Neither accept a leading $(D '^') as meaning the complement of the string (use the $(D 'c') modifier for that). + + See_Also: + $(LREF translate), + $(REF replace, std,array), + $(REF substitute, std,algorithm,iteration) +/ C1[] tr(C1, C2, C3, C4 = immutable char) (C1[] str, const(C2)[] from, const(C3)[] to, const(C4)[] modifiers = null) From 33cf85fdb443aa68b3612f5e67b058dfad350787 Mon Sep 17 00:00:00 2001 From: Sebastian Wilzbach Date: Wed, 17 Jan 2018 04:38:06 +0100 Subject: [PATCH 3/4] Address review --- std/algorithm/iteration.d | 97 ++++++++++++++++++++++----------------- std/range/package.d | 7 --- 2 files changed, 54 insertions(+), 50 deletions(-) diff --git a/std/algorithm/iteration.d b/std/algorithm/iteration.d index 23d743c1d..536c8f716 100644 --- a/std/algorithm/iteration.d +++ b/std/algorithm/iteration.d @@ -4722,49 +4722,53 @@ if (substs.length >= 2 && isExpressions!substs) Complexity: $(BIGOH 1) due to D's $(D switch) guaranteeing $(BIGOH 1); */ auto substitute(Value)(Value value) - if (!isInputRange!Value && !is(CommonType!(Value, typeof(substs[0])) == void)) + if (isInputRange!Value || !is(CommonType!(Value, typeof(substs[0])) == void)) { - switch (value) + static if (isInputRange!Value) { - static foreach (i; 0 .. substs.length / 2) - case substs[2 * i]: - return substs[2 * i + 1]; - - default: return value; + static if (!is(CommonType!(ElementType!Value, typeof(substs[0])) == void)) + { + // Substitute single range elements with compile-time substitution mappings + return value.map!(a => substitute(a)); + } + else static if (isInputRange!Value && !is(CommonType!(ElementType!Value, ElementType!(typeof(substs[0]))) == void)) + { + // not implemented yet, fallback to runtime variant for now + return .substitute(value, substs); + } + else + { + static assert(0, "Compile-time substitutions must be elements or ranges of the same type of ` ~ Value.stringof ~ `."); + } } - } + // Substitute single values with compile-time substitution mappings. + else // static if (!is(CommonType!(Value, typeof(substs[0])) == void)) + { + switch (value) + { + static foreach (i; 0 .. substs.length / 2) + case substs[2 * i]: + return substs[2 * i + 1]; - /// Substitute single range elements with compile-time substitution mappings - auto substitute(R)(R r) - if (isInputRange!R && !is(CommonType!(ElementType!R, typeof(substs[0])) == void)) - { - return r.map!(a => substitute(a)); - } - - /// Substitute subranges with compile-time substitution mappings - auto substitute(R)(R r) - if (isInputRange!R && !is(CommonType!(ElementType!R, ElementType!(typeof(substs[0]))) == void)) - { - // not implemented yet, fallback to runtime variant for now - return .substitute(r, substs); + default: return value; + } + } } } // In same combinations substitute needs to calculate the auto-decoded length // of its needles -private template needsManualAutodecoding(Range, Needles...) +private template hasDifferentAutodecoding(Range, Needles...) { import std.meta : anySatisfy; /* iff - - the needles needs auto-decoding, but the incoming range doesn't - - (vice versa) if the incoming range needs auto-decoding, but the needle don't + - the needles needs auto-decoding, but the incoming range doesn't (or vice versa) - both (range, needle) need auto-decoding and don't share the same common type */ enum needlesAreNarrow = anySatisfy!(isNarrowString, Needles); enum sourceIsNarrow = isNarrowString!Range; - enum needsManualAutodecoding = ( sourceIsNarrow && !needlesAreNarrow) || - (!sourceIsNarrow && needlesAreNarrow) || - ((sourceIsNarrow && needlesAreNarrow) && + enum hasDifferentAutodecoding = sourceIsNarrow != needlesAreNarrow || + (sourceIsNarrow && needlesAreNarrow && is(CommonType!(Range, Needles) == void)); } @@ -4772,18 +4776,25 @@ private template needsManualAutodecoding(Range, Needles...) { import std.meta : AliasSeq; // used for better clarity - static assert(!needsManualAutodecoding!(string, AliasSeq!(string, string))); - static assert(!needsManualAutodecoding!(wstring, AliasSeq!(wstring, wstring))); - static assert(!needsManualAutodecoding!(dstring, AliasSeq!(dstring, dstring))); + static assert(!hasDifferentAutodecoding!(string, AliasSeq!(string, string))); + static assert(!hasDifferentAutodecoding!(wstring, AliasSeq!(wstring, wstring))); + static assert(!hasDifferentAutodecoding!(dstring, AliasSeq!(dstring, dstring))); - static assert(needsManualAutodecoding!(string, AliasSeq!(wstring, string))); - static assert(needsManualAutodecoding!(wstring, AliasSeq!(string, string))); + // the needles needs auto-decoding, but the incoming range doesn't (or vice versa) + static assert(hasDifferentAutodecoding!(string, AliasSeq!(wstring, wstring))); + static assert(hasDifferentAutodecoding!(string, AliasSeq!(dstring, dstring))); + static assert(hasDifferentAutodecoding!(wstring, AliasSeq!(string, string))); + static assert(hasDifferentAutodecoding!(wstring, AliasSeq!(dstring, dstring))); + static assert(hasDifferentAutodecoding!(dstring, AliasSeq!(string, string))); + static assert(hasDifferentAutodecoding!(dstring, AliasSeq!(wstring, wstring))); - static assert(needsManualAutodecoding!(dstring, AliasSeq!(string, string))); - static assert(needsManualAutodecoding!(dstring, AliasSeq!(string, wstring))); - - static assert(needsManualAutodecoding!(string, AliasSeq!(dstring, string))); - static assert(needsManualAutodecoding!(string, AliasSeq!(wstring, string))); + // both (range, needle) need auto-decoding and don't share the same common type + static foreach (T; AliasSeq!(string, wstring, dstring)) + { + static assert(hasDifferentAutodecoding!(T, AliasSeq!(wstring, string))); + static assert(hasDifferentAutodecoding!(T, AliasSeq!(dstring, string))); + static assert(hasDifferentAutodecoding!(T, AliasSeq!(wstring, dstring))); + } } /// ditto @@ -4880,10 +4891,10 @@ if (isInputRange!R && Substs.length >= 2 && !is(CommonType!(Substs) == void)) Hit hitNr; // hit number: 0 means no hit, otherwise index+1 to needles that matched bool hasHit; // is there a replacement hit which should be printed? - enum needsManualAutodecoding = .needsManualAutodecoding!(typeof(rest), Ins); + enum hasDifferentAutodecoding = .hasDifferentAutodecoding!(typeof(rest), Ins); // calculating the needle length for narrow strings might be expensive -> cache it - static if (needsManualAutodecoding) + static if (hasDifferentAutodecoding) ptrdiff_t[n] needleLengths = -1; } @@ -4960,12 +4971,12 @@ if (isInputRange!R && Substs.length >= 2 && !is(CommonType!(Substs) == void)) else { auto hitLength = size_t.max; - SWITCH: switch (hitNr - 1) + switchL: switch (hitNr - 1) { static foreach (i; 0 .. n) { case i: - static if (needsManualAutodecoding) + static if (hasDifferentAutodecoding) { import std.utf : codeLength; @@ -4979,7 +4990,7 @@ if (isInputRange!R && Substs.length >= 2 && !is(CommonType!(Substs) == void)) { hitLength = needles[i].length; } - break SWITCH; + break switchL; } default: assert(0, "hitNr should always be found"); @@ -4993,7 +5004,7 @@ if (isInputRange!R && Substs.length >= 2 && !is(CommonType!(Substs) == void)) // iff the source range and the substitutions are narrow strings, // we can avoid calling the auto-decoding `popFront` (via drop) - static if (isNarrowString!(typeof(hitValue)) && !needsManualAutodecoding) + static if (isNarrowString!(typeof(hitValue)) && !hasDifferentAutodecoding) rest = hitValue[hitLength .. $]; else rest = hitValue.drop(hitLength); diff --git a/std/range/package.d b/std/range/package.d index 5d62c2b19..e930e16dc 100644 --- a/std/range/package.d +++ b/std/range/package.d @@ -2010,13 +2010,6 @@ if (isInputRange!(Unqual!Range) && return _maxAvailable == 0 || source.empty; } - static if (is(typeof((cast(const R) source).empty))) - /// ditto - @property bool empty() const - { - return _maxAvailable == 0 || source.empty; - } - /// ditto @property auto ref front() { From 15835daa7c152597fc6e01ff78aa5c8a150113b1 Mon Sep 17 00:00:00 2001 From: Sebastian Wilzbach Date: Thu, 25 Jan 2018 09:32:54 +0100 Subject: [PATCH 4/4] Fix DAutotest --- changelog/std-algorithm-iteration-substitute.dd | 2 +- std/algorithm/iteration.d | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog/std-algorithm-iteration-substitute.dd b/changelog/std-algorithm-iteration-substitute.dd index 20557c03e..6a8fa37e3 100644 --- a/changelog/std-algorithm-iteration-substitute.dd +++ b/changelog/std-algorithm-iteration-substitute.dd @@ -1,7 +1,7 @@ `std.algorithm.searching.substitute` was added $(REF substitute, std,algorithm,searching) yields a lazy range with -all occurrences of $(D substs) in $(R r) replaced with their substitution: +all occurrences of `substs` in `r` replaced with their substitution: --- import std.algorithm.comparison : equal; diff --git a/std/algorithm/iteration.d b/std/algorithm/iteration.d index 536c8f716..f0c2b5d2e 100644 --- a/std/algorithm/iteration.d +++ b/std/algorithm/iteration.d @@ -48,7 +48,7 @@ $(T2 reduce, $(T2 splitter, Lazily splits a range by a separator.) $(T2 substitute, - $(D [1, 2].substitute(1, 0.1)) returns $(D [0.1, 2]).) + `[1, 2].substitute(1, 0.1)` returns `[0.1, 2]`.) $(T2 sum, Same as `fold`, but specialized for accurate summation.) $(T2 uniq,