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...) uint startsWith(alias pred = "a == b", Range, Ranges...)
(Range doesThisStart, Ranges withOneOfThese) (Range doesThisStart, Ranges withOneOfThese)
if (Ranges.length > 1 && isInputRange!Range if(isInputRange!Range && Ranges.length > 1 &&
&& is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[0])) is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[0])) : bool ) &&
: bool) is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[1 .. $])) : uint))
&& is(typeof(.startsWith!pred(doesThisStart, withOneOfThese[1 .. $]))
: uint))
{ {
alias doesThisStart lhs; alias doesThisStart haystack;
alias withOneOfThese rhs; alias withOneOfThese needles;
// Make one pass looking for empty ranges // Make one pass looking for empty ranges
foreach (i, Unused; Ranges) foreach(i, Unused; Ranges)
{ {
// Empty range matches everything // 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 // Single-element
if (binaryFun!pred(lhs.front, rhs[i])) if (binaryFun!pred(haystack.front, needles[i]))
{ {
// found, but continue to account for one-element // found, but continue to account for one-element
// range matches (consider startsWith("ab", "a", // range matches (consider startsWith("ab", "a",
@ -3779,75 +3778,90 @@ if (Ranges.length > 1 && isInputRange!Range
} }
else else
{ {
if (binaryFun!pred(lhs.front, rhs[i].front)) if(binaryFun!pred(haystack.front, needles[i].front))
{
continue; continue;
} }
}
// This code executed on failure to match // This code executed on failure to match
// Out with this guy, check for the others // Out with this guy, check for the others
uint result = startsWith!pred(lhs, rhs[0 .. i], rhs[i + 1 .. $]); uint result = startsWith!pred(haystack, needles[0 .. i], needles[i + 1 .. $]);
if (result > i) ++result; if(result > i)
++result;
return result; return result;
} }
// If execution reaches this point, then the front matches for all // 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. // 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 // Test has passed in the previous loop
return i + 1; return i + 1;
} }
else else
{ {
rhs[i].popFront(); needles[i].popFront();
if (rhs[i].empty) return i + 1; if(needles[i].empty)
return i + 1;
} }
} }
} }
return 0; return 0;
} }
/// Ditto /// Ditto
bool startsWith(alias pred = "a == b", R1, R2) bool startsWith(alias pred = "a == b", R1, R2)
(R1 doesThisStart, R2 withThis) (R1 doesThisStart, R2 withThis)
if (isInputRange!R1 && isInputRange!R2 if(isInputRange!R1 &&
&& is(typeof(binaryFun!pred(doesThisStart.front, withThis.front)) isInputRange!R2 &&
: bool)) is(typeof(binaryFun!pred(doesThisStart.front, withThis.front)) : bool))
{ {
// Special case for two arrays
static if (isArray!R1 && isArray!R2)
{
alias doesThisStart haystack; alias doesThisStart haystack;
alias withThis needle; alias withThis needle;
//writeln("Matching: ", haystack, " with ", needle);
if (haystack.length < needle.length) return 0; // Special case for two arrays
foreach (j; 0 .. needle.length) static if(isArray!R1 && isArray!R2 &&
((!isSomeString!R1 && !isSomeString!R2) ||
(isSomeString!R1 && isSomeString!R2 &&
is(Unqual!(typeof(haystack[0])) == Unqual!(typeof(needle[0]))))))
{ {
if (!binaryFun!pred(needle[j], haystack[j])) if(haystack.length < needle.length)
return false;
foreach(j; 0 .. needle.length)
{
if(!binaryFun!pred(needle[j], haystack[j]))
// not found // not found
return false; return false;
} }
// found! // found!
return true; return true;
} }
else 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; break;
withThis.popFront();
if (withThis.empty) return true; needle.popFront();
if(needle.empty)
return true;
} }
return false; return false;
} }
@ -3855,12 +3869,11 @@ if (isInputRange!R1 && isInputRange!R2
/// Ditto /// Ditto
bool startsWith(alias pred = "a == b", R, E) bool startsWith(alias pred = "a == b", R, E)
(R doesThisStart, E withThis) (R doesThisStart, E withThis)
if (isInputRange!R && is(typeof(binaryFun!pred(doesThisStart.front, withThis)) if(isInputRange!R &&
: bool)) is(typeof(binaryFun!pred(doesThisStart.front, withThis)) : bool))
{ {
return doesThisStart.empty return doesThisStart.empty ? false
? false
: binaryFun!pred(doesThisStart.front, withThis); : binaryFun!pred(doesThisStart.front, withThis);
} }
@ -3868,29 +3881,69 @@ unittest
{ {
debug(std_algorithm) scope(success) debug(std_algorithm) scope(success)
writeln("unittest @", __FILE__, ":", __LINE__, " done."); 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 foreach(S; TypeTuple!(char[], wchar[], dchar[], string, wstring, dstring))
{ {
debug(std_algorithm) scope(success) assert(!startsWith(to!S("abc"), 'a'));
writeln("unittest @", __FILE__, ":", __LINE__, " done."); assert(startsWith(to!S("abc"), 'a', 'c') == 1);
assert(!startsWith("abc", 'x', 'n', 'b')); assert(!startsWith(to!S("abc"), 'x', 'n', 'b'));
assert(startsWith("abc", 'x', 'n', 'a') == 3); 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); assert(endsWith("abc", "x", "aaa", 'c', "sab") == 3);
---- ----
*/ */
uint uint endsWith(alias pred = "a == b", Range, Ranges...)
endsWith(alias pred = "a == b", Range, Ranges...) (Range doesThisEnd, Ranges withOneOfThese)
(Range doesThisEnd, Ranges withOneOfThese) if(isInputRange!Range && Ranges.length > 1 &&
if (isInputRange!(Range) && Ranges.length > 0 is(typeof(.endsWith!pred(doesThisEnd, withOneOfThese[0])) : bool) &&
&& is(typeof(binaryFun!pred(doesThisEnd.back, withOneOfThese[0].back)))) is(typeof(.endsWith!pred(doesThisEnd, withOneOfThese[1 .. $])) : uint))
{ {
alias doesThisEnd lhs; alias doesThisEnd haystack;
alias withOneOfThese rhs; alias withOneOfThese needles;
// Special case for two arrays
static if (Ranges.length == 1 && isArray!Range && isArray!(Ranges[0])
&& is(typeof(binaryFun!(pred)(lhs[0], rhs[0][0]))))
{
if (lhs.length < rhs[0].length) return 0;
auto k = lhs.length - rhs[0].length;
foreach (j; 0 .. rhs[0].length)
{
if (!binaryFun!(pred)(rhs[0][j], lhs[j + k]))
// not found
return 0u;
}
// found!
return 1u;
}
else
{
// Make one pass looking for empty ranges // Make one pass looking for empty ranges
foreach (i, Unused; Ranges) foreach(i, Unused; Ranges)
{ {
// Empty range matches everything // Empty range matches everything
if (rhs[i].empty) return i + 1; static if(!is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool))
{
if(needles[i].empty)
return i + 1;
} }
bool mismatch[Ranges.length]; }
for (; !lhs.empty; lhs.popBack)
for(; !haystack.empty; haystack.popBack())
{ {
foreach (i, Unused; Ranges) foreach(i, Unused; Ranges)
{ {
if (mismatch[i]) continue; static if(is(typeof(binaryFun!pred(haystack.back, needles[i])) : bool))
if (binaryFun!pred(lhs.back, rhs[i].back))
{ {
// Stay in the game // Single-element
rhs[i].popBack(); if(binaryFun!pred(haystack.back, needles[i]))
// Done with success if exhausted {
if (rhs[i].empty) return i + 1; // found, but continue to account for one-element
// range matches (consider endsWith("ab", "b",
// 'b') should return 1, not 2).
continue;
}
} }
else else
{ {
// Out if(binaryFun!pred(haystack.back, needles[i].back))
mismatch[i] = true; continue;
} }
}
}
return 0;
}
}
unittest // This code executed on failure to match
{ // Out with this guy, check for the others
debug(std_algorithm) scope(success) uint result = endsWith!pred(haystack, needles[0 .. i], needles[i + 1 .. $]);
writeln("unittest @", __FILE__, ":", __LINE__, " done."); if(result > i)
assert(endsWith("abc", "")); ++result;
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));
}
/** return result;
Checks whether $(D doesThisEnd) starts with one of the individual }
elements $(D withOneOfThese) according to $(D pred).
Example: // 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
assert(endsWith("abc", 'x', 'c', 'a') == 2); // all ranges involved and recurse.
---- foreach(i, Unused; Ranges)
*/
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; 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; 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 unittest
{ {
debug(std_algorithm) scope(success) debug(std_algorithm) scope(success)
writeln("unittest @", __FILE__, ":", __LINE__, " done."); 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 // findAdjacent

View file

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