Improved startsWith and endsWith.

1. startsWith and endsWith now have similar implementations.

2. They work with strings of different character sizes, whereas before
they treated strings as arrays regardless of whether they had the same
character size or not.

3. endsWith now actually works when mixing ranges and elements in the
endsWith portion like the documentation claims it does.
This commit is contained in:
jmdavis 2011-05-29 02:30:31 -07:00
parent 06dd205d28
commit fcb672f55f
2 changed files with 316 additions and 157 deletions

View file

@ -3742,34 +3742,33 @@ assert(startsWith("abc", "x", "aaa", "a", "sab") == 3);
----
*/
uint startsWith(alias pred = "a == b", Range, Ranges...)
(Range doesThisStart, Ranges withOneOfThese)
if (Ranges.length > 1 && isInputRange!Range
&& is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[0]))
: bool)
&& is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[1 .. $]))
: uint))
(Range doesThisStart, Ranges withOneOfThese)
if(isInputRange!Range && Ranges.length > 1 &&
is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[0])) : bool ) &&
is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[1 .. $])) : uint))
{
alias doesThisStart lhs;
alias withOneOfThese rhs;
alias doesThisStart haystack;
alias withOneOfThese needles;
// Make one pass looking for empty ranges
foreach (i, Unused; Ranges)
foreach(i, Unused; Ranges)
{
// Empty range matches everything
static if (!is(typeof(binaryFun!pred(lhs.front, rhs[i])) : bool))
static if(!is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool))
{
if (rhs[i].empty) return i + 1;
if(needles[i].empty)
return i + 1;
}
}
for (; !lhs.empty; lhs.popFront())
for(; !haystack.empty; haystack.popFront())
{
foreach (i, Unused; Ranges)
foreach(i, Unused; Ranges)
{
static if (is(typeof(binaryFun!pred(lhs.front, rhs[i])) : bool))
static if(is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool))
{
// Single-element
if (binaryFun!pred(lhs.front, rhs[i]))
if (binaryFun!pred(haystack.front, needles[i]))
{
// found, but continue to account for one-element
// range matches (consider startsWith("ab", "a",
@ -3779,75 +3778,90 @@ if (Ranges.length > 1 && isInputRange!Range
}
else
{
if (binaryFun!pred(lhs.front, rhs[i].front))
{
if(binaryFun!pred(haystack.front, needles[i].front))
continue;
}
}
// This code executed on failure to match
// Out with this guy, check for the others
uint result = startsWith!pred(lhs, rhs[0 .. i], rhs[i + 1 .. $]);
if (result > i) ++result;
uint result = startsWith!pred(haystack, needles[0 .. i], needles[i + 1 .. $]);
if(result > i)
++result;
return result;
}
// If execution reaches this point, then the front matches for all
// rhs ranges. What we need to do now is to lop off the front of
// needles ranges. What we need to do now is to lop off the front of
// all ranges involved and recurse.
foreach (i, Unused; Ranges)
foreach(i, Unused; Ranges)
{
static if (is(typeof(binaryFun!pred(lhs.front, rhs[i])) : bool))
static if(is(typeof(binaryFun!pred(haystack.front, needles[i])) : bool))
{
// Test has passed in the previous loop
return i + 1;
}
else
{
rhs[i].popFront();
if (rhs[i].empty) return i + 1;
needles[i].popFront();
if(needles[i].empty)
return i + 1;
}
}
}
return 0;
}
/// Ditto
bool startsWith(alias pred = "a == b", R1, R2)
(R1 doesThisStart, R2 withThis)
if (isInputRange!R1 && isInputRange!R2
&& is(typeof(binaryFun!pred(doesThisStart.front, withThis.front))
: bool))
(R1 doesThisStart, R2 withThis)
if(isInputRange!R1 &&
isInputRange!R2 &&
is(typeof(binaryFun!pred(doesThisStart.front, withThis.front)) : bool))
{
alias doesThisStart haystack;
alias withThis needle;
// Special case for two arrays
static if (isArray!R1 && isArray!R2)
static if(isArray!R1 && isArray!R2 &&
((!isSomeString!R1 && !isSomeString!R2) ||
(isSomeString!R1 && isSomeString!R2 &&
is(Unqual!(typeof(haystack[0])) == Unqual!(typeof(needle[0]))))))
{
alias doesThisStart haystack;
alias withThis needle;
//writeln("Matching: ", haystack, " with ", needle);
if (haystack.length < needle.length) return 0;
foreach (j; 0 .. needle.length)
if(haystack.length < needle.length)
return false;
foreach(j; 0 .. needle.length)
{
if (!binaryFun!pred(needle[j], haystack[j]))
if(!binaryFun!pred(needle[j], haystack[j]))
// not found
return false;
}
// found!
return true;
}
else
{
static if (hasLength!R1 && hasLength!R2)
static if(hasLength!R1 && hasLength!R2)
{
if (doesThisStart.length < withThis.length) return false;
if(haystack.length < needle.length)
return false;
}
if (withThis.empty) return true;
for (; !doesThisStart.empty; doesThisStart.popFront())
if(needle.empty)
return true;
for(; !haystack.empty; haystack.popFront())
{
if (!binaryFun!pred(doesThisStart.front, withThis.front))
if(!binaryFun!pred(haystack.front, needle.front))
break;
withThis.popFront();
if (withThis.empty) return true;
needle.popFront();
if(needle.empty)
return true;
}
return false;
}
@ -3855,42 +3869,81 @@ if (isInputRange!R1 && isInputRange!R2
/// Ditto
bool startsWith(alias pred = "a == b", R, E)
(R doesThisStart, E withThis)
if (isInputRange!R && is(typeof(binaryFun!pred(doesThisStart.front, withThis))
: bool))
(R doesThisStart, E withThis)
if(isInputRange!R &&
is(typeof(binaryFun!pred(doesThisStart.front, withThis)) : bool))
{
return doesThisStart.empty
? false
: binaryFun!pred(doesThisStart.front, withThis);
return doesThisStart.empty ? false
: binaryFun!pred(doesThisStart.front, withThis);
}
unittest
{
debug(std_algorithm) scope(success)
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
bool x = startsWith("ab", "a");
assert(startsWith("abc", ""));
assert(startsWith("abc", "a"));
assert(!startsWith("abc", "b"));
assert(!startsWith("abc", "b", "bc", "abcd", "xyz"));
assert(startsWith("abc", "a", "b") == 1);
assert(startsWith("abc", "b", "a") == 2);
assert(startsWith("abc", "a", 'a') == 1);
assert(startsWith("abc", 'a', "a") == 1);
assert(startsWith("abc", "x", "a", "b") == 2);
assert(startsWith("abc", "x", "aa", "ab") == 3);
assert(startsWith("abc", "x", "aaa", "sab") == 0);
assert(startsWith("abc", 'a'));
assert(!startsWith("abc", "sab"));
assert(startsWith("abc", 'x', "aaa", 'a', "sab") == 3);
}
unittest
{
debug(std_algorithm) scope(success)
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
assert(!startsWith("abc", 'x', 'n', 'b'));
assert(startsWith("abc", 'x', 'n', 'a') == 3);
foreach(S; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring))
{
assert(!startsWith(to!S("abc"), 'a'));
assert(startsWith(to!S("abc"), 'a', 'c') == 1);
assert(!startsWith(to!S("abc"), 'x', 'n', 'b'));
assert(startsWith(to!S("abc"), 'x', 'n', 'a') == 3);
assert(startsWith(to!S("\uFF28abc"), 'a', '\uFF28', 'c') == 2);
foreach(T; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring))
{
assert(startsWith(to!S("abc"), to!T("")));
assert(startsWith(to!S("ab"), to!T("a")));
assert(startsWith(to!S("abc"), to!T("a")));
assert(!startsWith(to!S("abc"), to!T("b")));
assert(!startsWith(to!S("abc"), to!T("b"), "bc", "abcd", "xyz"));
assert(startsWith(to!S("abc"), to!T("ab"), 'a') == 1);
assert(startsWith(to!S("abc"), to!T("a"), "b") == 1);
assert(startsWith(to!S("abc"), to!T("b"), "a") == 2);
assert(startsWith(to!S("abc"), to!T("a"), 'a') == 1);
assert(startsWith(to!S("abc"), 'a', to!T("a")) == 1);
assert(startsWith(to!S("abc"), to!T("x"), "a", "b") == 2);
assert(startsWith(to!S("abc"), to!T("x"), "aa", "ab") == 3);
assert(startsWith(to!S("abc"), to!T("x"), "aaa", "sab") == 0);
assert(startsWith(to!S("abc"), 'a'));
assert(!startsWith(to!S("abc"), to!T("sab")));
assert(startsWith(to!S("abc"), 'x', to!T("aaa"), 'a', "sab") == 3);
assert(startsWith(to!S("\uFF28el\uFF4co"), to!T("\uFF28el")));
assert(startsWith(to!S("\uFF28el\uFF4co"), to!T("Hel"), to!T("\uFF28el")) == 2);
}
}
assert(startsWith([0, 1, 2, 3, 4, 5], cast(int[])null));
assert(!startsWith([0, 1, 2, 3, 4, 5], 5));
assert(!startsWith([0, 1, 2, 3, 4, 5], 1));
assert(startsWith([0, 1, 2, 3, 4, 5], 0));
assert(startsWith([0, 1, 2, 3, 4, 5], 5, 1, 2) == 3);
assert(startsWith([0, 1, 2, 3, 4, 5], [0]));
assert(startsWith([0, 1, 2, 3, 4, 5], [0, 1]));
assert(startsWith([0, 1, 2, 3, 4, 5], [0, 1], 7) == 1);
assert(!startsWith([0, 1, 2, 3, 4, 5], [0, 1, 7]));
assert(startsWith([0, 1, 2, 3, 4, 5], [0, 1, 7], [0, 1, 2]) == 2);
assert(!startsWith([0, 1, 2, 3, 4, 5], 1));
assert(startsWith([0, 1, 2, 3, 4, 5], 0));
assert(startsWith([0, 1, 2, 3, 4, 5], 5, 1, 2) == 3);
assert(startsWith([0, 1, 2, 3, 4, 5], [0]));
assert(startsWith([0, 1, 2, 3, 4, 5], [0, 1]));
assert(startsWith([0, 1, 2, 3, 4, 5], [0, 1], 7) == 1);
assert(!startsWith([0, 1, 2, 3, 4, 5], [0, 1, 7]));
assert(startsWith([0, 1, 2, 3, 4, 5], [0, 1, 7], [0, 1, 2]) == 2);
assert(!startsWith(filter!"true"([0, 1, 2, 3, 4, 5]), 1));
assert(startsWith(filter!"true"([0, 1, 2, 3, 4, 5]), 0));
assert(startsWith(filter!"true"([0, 1, 2, 3, 4, 5]), [0]));
assert(startsWith(filter!"true"([0, 1, 2, 3, 4, 5]), [0, 1]));
assert(startsWith(filter!"true"([0, 1, 2, 3, 4, 5]), [0, 1], 7) == 1);
assert(!startsWith(filter!"true"([0, 1, 2, 3, 4, 5]), [0, 1, 7]));
assert(startsWith(filter!"true"([0, 1, 2, 3, 4, 5]), [0, 1, 7], [0, 1, 2]) == 2);
assert(startsWith([0, 1, 2, 3, 4, 5], filter!"true"([0, 1])));
assert(startsWith([0, 1, 2, 3, 4, 5], filter!"true"([0, 1]), 7));
assert(!startsWith([0, 1, 2, 3, 4, 5], filter!"true"([0, 1, 7])));
assert(startsWith([0, 1, 2, 3, 4, 5], [0, 1, 7], filter!"true"([0, 1, 2])) == 2);
}
/**
@ -3984,111 +4037,217 @@ assert(endsWith("abc", "x", "aaa", "sab") == 0);
assert(endsWith("abc", "x", "aaa", 'c', "sab") == 3);
----
*/
uint
endsWith(alias pred = "a == b", Range, Ranges...)
(Range doesThisEnd, Ranges withOneOfThese)
if (isInputRange!(Range) && Ranges.length > 0
&& is(typeof(binaryFun!pred(doesThisEnd.back, withOneOfThese[0].back))))
uint endsWith(alias pred = "a == b", Range, Ranges...)
(Range doesThisEnd, Ranges withOneOfThese)
if(isInputRange!Range && Ranges.length > 1 &&
is(typeof(.endsWith!pred(doesThisEnd, withOneOfThese[0])) : bool) &&
is(typeof(.endsWith!pred(doesThisEnd, withOneOfThese[1 .. $])) : uint))
{
alias doesThisEnd lhs;
alias withOneOfThese rhs;
// Special case for two arrays
static if (Ranges.length == 1 && isArray!Range && isArray!(Ranges[0])
&& is(typeof(binaryFun!(pred)(lhs[0], rhs[0][0]))))
alias doesThisEnd haystack;
alias withOneOfThese needles;
// Make one pass looking for empty ranges
foreach(i, Unused; Ranges)
{
if (lhs.length < rhs[0].length) return 0;
auto k = lhs.length - rhs[0].length;
foreach (j; 0 .. rhs[0].length)
// Empty range matches everything
static if(!is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool))
{
if (!binaryFun!(pred)(rhs[0][j], lhs[j + k]))
// not found
return 0u;
if(needles[i].empty)
return i + 1;
}
// found!
return 1u;
}
else
for(; !haystack.empty; haystack.popBack())
{
// Make one pass looking for empty ranges
foreach (i, Unused; Ranges)
foreach(i, Unused; Ranges)
{
// Empty range matches everything
if (rhs[i].empty) return i + 1;
}
bool mismatch[Ranges.length];
for (; !lhs.empty; lhs.popBack)
{
foreach (i, Unused; Ranges)
static if(is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool))
{
if (mismatch[i]) continue;
if (binaryFun!pred(lhs.back, rhs[i].back))
// Single-element
if(binaryFun!pred(haystack.back, needles[i]))
{
// Stay in the game
rhs[i].popBack();
// Done with success if exhausted
if (rhs[i].empty) return i + 1;
}
else
{
// Out
mismatch[i] = true;
// found, but continue to account for one-element
// range matches (consider endsWith("ab", "b",
// 'b') should return 1, not 2).
continue;
}
}
else
{
if(binaryFun!pred(haystack.back, needles[i].back))
continue;
}
// This code executed on failure to match
// Out with this guy, check for the others
uint result = endsWith!pred(haystack, needles[0 .. i], needles[i + 1 .. $]);
if(result > i)
++result;
return result;
}
// If execution reaches this point, then the back matches for all
// needles ranges. What we need to do now is to lop off the back of
// all ranges involved and recurse.
foreach(i, Unused; Ranges)
{
static if(is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool))
{
// Test has passed in the previous loop
return i + 1;
}
else
{
needles[i].popBack();
if(needles[i].empty)
return i + 1;
}
}
return 0;
}
}
unittest
{
debug(std_algorithm) scope(success)
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
assert(endsWith("abc", ""));
assert(!endsWith("abc", "a"));
assert(!endsWith("abc", 'a'));
assert(!endsWith("abc", "b"));
assert(endsWith("abc", "a", "c") == 2);
assert(endsWith("abc", 'a', 'c') == 2);
assert(endsWith("abc", "c", "a") == 1);
assert(endsWith("abc", "c", "c") == 1);
assert(endsWith("abc", "x", "c", "b") == 2);
assert(endsWith("abc", "x", "aa", "bc") == 3);
assert(endsWith("abc", "x", "aaa", "sab") == 0);
assert(endsWith("abc", "x", "aaa", "c", "sab") == 3);
// string a = "abc";
// immutable(char[1]) b = "c";
// assert(wyda(a, b));
}
/**
Checks whether $(D doesThisEnd) starts with one of the individual
elements $(D withOneOfThese) according to $(D pred).
Example:
----
assert(endsWith("abc", 'x', 'c', 'a') == 2);
----
*/
uint endsWith(alias pred = "a == b", Range, Elements...)
(Range doesThisEnd, Elements withOneOfThese)
if (isInputRange!Range && Elements.length > 0
&& is(typeof(binaryFun!pred(doesThisEnd.front, withOneOfThese[0]))))
{
if (doesThisEnd.empty) return 0;
auto back = doesThisEnd.back;
foreach (i, Unused; Elements)
{
if (binaryFun!pred(back, withOneOfThese[i])) return i + 1;
}
return 0;
}
/// Ditto
bool endsWith(alias pred = "a == b", R1, R2)
(R1 doesThisEnd, R2 withThis)
if(isInputRange!R1 &&
isInputRange!R2 &&
is(typeof(binaryFun!pred(doesThisEnd.back, withThis.back)) : bool))
{
alias doesThisEnd haystack;
alias withThis needle;
// Special case for non-character arrays and strings of the same character type.
static if(isArray!R1 && isArray!R2 &&
((!isSomeString!R1 && !isSomeString!R2) ||
(isSomeString!R1 && isSomeString!R2 &&
is(Unqual!(typeof(haystack[0])) == Unqual!(typeof(needle[0]))))))
{
if(haystack.length < needle.length)
return false;
immutable diff = haystack.length - needle.length;
foreach(j; 0 .. needle.length)
{
if(!binaryFun!(pred)(needle[j], haystack[j + diff]))
// not found
return false;
}
// found!
return true;
}
else
{
static if(hasLength!R1 && hasLength!R2)
{
if(haystack.length < needle.length)
return false;
}
if(needle.empty)
return true;
for(; !haystack.empty; haystack.popBack())
{
if(!binaryFun!pred(haystack.back, needle.back))
break;
needle.popBack();
if(needle.empty)
return true;
}
return false;
}
}
/// Ditto
bool endsWith(alias pred = "a == b", R, E)
(R doesThisEnd, E withThis)
if(isInputRange!R &&
is(typeof(binaryFun!pred(doesThisEnd.back, withThis)) : bool))
{
return doesThisEnd.empty ? false
: binaryFun!pred(doesThisEnd.back, withThis);
}
unittest
{
debug(std_algorithm) scope(success)
writeln("unittest @", __FILE__, ":", __LINE__, " done.");
assert(!startsWith("abc", 'x', 'n', 'b'));
assert(startsWith("abc", 'x', 'n', 'a') == 3);
//This is because we need to run some tests on ranges which _aren't_ arrays,
//and as far as I can tell, all of the functions which would wrap an array
//in a range (such as filter) don't return bidirectional ranges, so I'm
//creating one here.
auto static wrap(R)(R r)
if(isBidirectionalRange!R)
{
struct Result
{
@property auto ref front() {return _range.front;}
@property auto ref back() {return _range.back;}
@property bool empty() {return _range.empty;}
void popFront() {_range.popFront();}
void popBack() {_range.popBack();}
R _range;
}
return Result(r);
}
foreach(S; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring))
{
assert(!endsWith(to!S("abc"), 'a'));
assert(endsWith(to!S("abc"), 'a', 'c') == 2);
assert(!endsWith(to!S("abc"), 'x', 'n', 'b'));
assert(endsWith(to!S("abc"), 'x', 'n', 'c') == 3);
assert(endsWith(to!S("abc\uFF28"), 'a', '\uFF28', 'c') == 2);
foreach(T; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring))
{
assert(endsWith(to!S("abc"), to!T("")));
assert(!endsWith(to!S("abc"), to!T("a")));
assert(!endsWith(to!S("abc"), to!T("b")));
assert(!endsWith(to!S("abc"), to!T("bc"), 'c') == 1);
assert(endsWith(to!S("abc"), to!T("a"), "c") == 2);
assert(endsWith(to!S("abc"), to!T("c"), "a") == 1);
assert(endsWith(to!S("abc"), to!T("c"), "c") == 1);
assert(endsWith(to!S("abc"), to!T("x"), 'c', "b") == 2);
assert(endsWith(to!S("abc"), 'x', to!T("aa"), "bc") == 3);
assert(endsWith(to!S("abc"), to!T("x"), "aaa", "sab") == 0);
assert(endsWith(to!S("abc"), to!T("x"), "aaa", "c", "sab") == 3);
assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("l\uFF4co")));
assert(endsWith(to!S("\uFF28el\uFF4co"), to!T("lo"), to!T("l\uFF4co")) == 2);
}
}
assert(endsWith([0, 1, 2, 3, 4, 5], cast(int[])null));
assert(!endsWith([0, 1, 2, 3, 4, 5], 0));
assert(!endsWith([0, 1, 2, 3, 4, 5], 4));
assert(endsWith([0, 1, 2, 3, 4, 5], 5));
assert(endsWith([0, 1, 2, 3, 4, 5], 0, 4, 5) == 3);
assert(endsWith([0, 1, 2, 3, 4, 5], [5]));
assert(endsWith([0, 1, 2, 3, 4, 5], [4, 5]));
assert(endsWith([0, 1, 2, 3, 4, 5], [4, 5], 7) == 1);
assert(!endsWith([0, 1, 2, 3, 4, 5], [2, 4, 5]));
assert(endsWith([0, 1, 2, 3, 4, 5], [2, 4, 5], [3, 4, 5]) == 2);
assert(!endsWith(wrap([0, 1, 2, 3, 4, 5]), 4));
assert(endsWith(wrap([0, 1, 2, 3, 4, 5]), 5));
assert(endsWith(wrap([0, 1, 2, 3, 4, 5]), [5]));
assert(endsWith(wrap([0, 1, 2, 3, 4, 5]), [4, 5]));
assert(endsWith(wrap([0, 1, 2, 3, 4, 5]), [4, 5], 7) == 1);
assert(!endsWith(wrap([0, 1, 2, 3, 4, 5]), [2, 4, 5]));
assert(endsWith(wrap([0, 1, 2, 3, 4, 5]), [2, 4, 5], [3, 4, 5]) == 2);
assert(endsWith([0, 1, 2, 3, 4, 5], wrap([4, 5])));
assert(endsWith([0, 1, 2, 3, 4, 5], wrap([4, 5]), 7));
assert(!endsWith([0, 1, 2, 3, 4, 5], wrap([2, 4, 5])));
assert(endsWith([0, 1, 2, 3, 4, 5], [2, 4, 5], wrap([3, 4, 5])) == 2);
}
// findAdjacent

View file

@ -1363,7 +1363,7 @@ C[] chomp(C)(C[] s)
}
/// Ditto
C[] chomp(C, C1)(C[] s, in C1[] delimiter)
C[] chomp(C, C1)(C[] s, const(C1)[] delimiter)
{
if (endsWith(s, delimiter))
{