Merge pull request #720 from jmdavis/8334

Fix for issue# 8334: find cannot handle close match at end of haystack in needle isn't bi-directional
This commit is contained in:
Andrei Alexandrescu 2012-08-19 21:00:27 -07:00
commit 47204076d8
2 changed files with 236 additions and 27 deletions

View file

@ -3201,8 +3201,8 @@ unittest
// Leftover specialization: searching a random-access range for a // Leftover specialization: searching a random-access range for a
// non-bidirectional forward range // non-bidirectional forward range
R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle) R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
if (isRandomAccessRange!R1 && isForwardRange!R2 && !isBidirectionalRange!R2 if (isRandomAccessRange!R1 && isForwardRange!R2 && !isBidirectionalRange!R2 &&
&& is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)) is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool))
{ {
static if (!is(ElementType!R1 == ElementType!R2)) static if (!is(ElementType!R1 == ElementType!R2))
{ {
@ -3211,11 +3211,25 @@ if (isRandomAccessRange!R1 && isForwardRange!R2 && !isBidirectionalRange!R2
else else
{ {
// Prepare the search with needle's first element // Prepare the search with needle's first element
if (needle.empty) return haystack; if (needle.empty)
return haystack;
haystack = .find!pred(haystack, needle.front); haystack = .find!pred(haystack, needle.front);
if (haystack.empty) return haystack;
static if (hasLength!R1 && hasLength!R2 && is(typeof(takeNone(haystack)) == typeof(haystack)))
{
if (needle.length > haystack.length)
return takeNone(haystack);
}
else
{
if (haystack.empty)
return haystack;
}
needle.popFront(); needle.popFront();
size_t matchLen = 1; size_t matchLen = 1;
// Loop invariant: haystack[0 .. matchLen] matches everything in // Loop invariant: haystack[0 .. matchLen] matches everything in
// the initial needle that was popped out of needle. // the initial needle that was popped out of needle.
for (;;) for (;;)
@ -3223,11 +3237,22 @@ if (isRandomAccessRange!R1 && isForwardRange!R2 && !isBidirectionalRange!R2
// Extend matchLength as much as possible // Extend matchLength as much as possible
for (;;) for (;;)
{ {
if (needle.empty || haystack.empty) return haystack; if (needle.empty || haystack.empty)
if (!binaryFun!pred(haystack[matchLen], needle.front)) break; return haystack;
static if(hasLength!R1 && is(typeof(takeNone(haystack)) == typeof(haystack)))
{
if(matchLen == haystack.length)
return takeNone(haystack);
}
if (!binaryFun!pred(haystack[matchLen], needle.front))
break;
++matchLen; ++matchLen;
needle.popFront(); needle.popFront();
} }
auto bestMatch = haystack[0 .. matchLen]; auto bestMatch = haystack[0 .. matchLen];
haystack.popFront(); haystack.popFront();
haystack = .find!pred(haystack, bestMatch); haystack = .find!pred(haystack, bestMatch);
@ -3241,6 +3266,19 @@ unittest
assert(find([ 1, 2, 1, 2, 3, 3 ], SList!int(2, 3)[]) == [ 2, 3, 3 ]); assert(find([ 1, 2, 1, 2, 3, 3 ], SList!int(2, 3)[]) == [ 2, 3, 3 ]);
} }
//Bug# 8334
unittest
{
auto haystack = [1, 2, 3, 4, 1, 9, 12, 42];
auto needle = [12, 42, 27];
//different overload of find, but it's the base case.
assert(find(haystack, needle).empty);
assert(find(haystack, takeExactly(filter!"true"(needle), 3)).empty);
assert(find(haystack, filter!"true"(needle)).empty);
}
// Internally used by some find() overloads above. Can't make it // Internally used by some find() overloads above. Can't make it
// private due to bugs in the compiler. // private due to bugs in the compiler.
/*private*/ R1 simpleMindedFind(alias pred, R1, R2)(R1 haystack, R2 needle) /*private*/ R1 simpleMindedFind(alias pred, R1, R2)(R1 haystack, R2 needle)

View file

@ -2871,9 +2871,6 @@ Returns a range with at most one element; for example, $(D
takeOne([42, 43, 44])) returns a range consisting of the integer $(D takeOne([42, 43, 44])) returns a range consisting of the integer $(D
42). Calling $(D popFront()) off that range renders it empty. 42). Calling $(D popFront()) off that range renders it empty.
Sometimes an empty range with the same signature is needed. For such
ranges use $(D takeNone!R()). For example:
---- ----
auto s = takeOne([42, 43, 44]); auto s = takeOne([42, 43, 44]);
static assert(isRandomAccessRange!(typeof(s))); static assert(isRandomAccessRange!(typeof(s)));
@ -2887,19 +2884,15 @@ assert(s[0] == 43);
s.popFront(); s.popFront();
assert(s.length == 0); assert(s.length == 0);
assert(s.empty); assert(s.empty);
s = takeNone!(int[])();
assert(s.length == 0);
assert(s.empty);
---- ----
In effect $(D takeOne(r)) is somewhat equivalent to $(D take(r, 1)) and In effect $(D takeOne(r)) is somewhat equivalent to $(D take(r, 1)) but in
$(D takeNone(r)) is equivalent to $(D take(r, 0)), but in certain certain interfaces it is important to know statically that the range may only
interfaces it is important to know statically that the range may only
have at most one element. have at most one element.
The type returned by $(D takeOne) and $(D takeNone) is a random-access The type returned by $(D takeOne) is a random-access range with length
range with length regardless of $(D R)'s capability (another feature regardless of $(D R)'s capabilities (another feature that distinguishes
that distinguishes $(D takeOne)/$(D takeNone) from $(D take)). $(D takeOne) from $(D take)).
*/ */
auto takeOne(R)(R source) if (isInputRange!R) auto takeOne(R)(R source) if (isInputRange!R)
{ {
@ -2935,12 +2928,6 @@ auto takeOne(R)(R source) if (isInputRange!R)
} }
} }
/// Ditto
auto takeNone(R)() if (isInputRange!R)
{
return typeof(takeOne(R.init)).init;
}
unittest unittest
{ {
auto s = takeOne([42, 43, 44]); auto s = takeOne([42, 43, 44]);
@ -2955,9 +2942,193 @@ unittest
s.popFront(); s.popFront();
assert(s.length == 0); assert(s.length == 0);
assert(s.empty); assert(s.empty);
s = takeNone!(int[])(); }
assert(s.length == 0);
assert(s.empty); /++
Returns an empty range which is statically known to be empty and is
guaranteed to have $(D length) and be random access regardless of $(D R)'s
capabilities.
Examples:
--------------------
auto range = takeNone!(int[])();
assert(range.length == 0);
assert(range.empty);
--------------------
+/
auto takeNone(R)()
if(isInputRange!R)
{
return typeof(takeOne(R.init)).init;
}
unittest
{
auto range = takeNone!(int[])();
assert(range.length == 0);
assert(range.empty);
enum ctfe = takeNone!(int[])();
static assert(ctfe.length == 0);
static assert(ctfe.empty);
}
/++
Creates an empty range from the given range in $(BIGOH 1). If it can, it
will return the same range type. If not, it will return
$(D takeExactly(range, 0)).
Examples:
--------------------
assert(takeNone([42, 27, 19]).empty);
assert(takeNone("dlang.org").empty);
assert(takeNone(filter!"true"([42, 27, 19])).empty);
--------------------
+/
auto takeNone(R)(R range)
if(isInputRange!R)
{
//Makes it so that calls to takeNone which don't use UFCS still work with a
//member version if it's defined.
static if(is(typeof(R.takeNone)))
auto retval = range.takeNone();
//@@@BUG@@@ 8339
else static if(isDynamicArray!R)/+ ||
(is(R == struct) && __traits(compiles, {auto r = R.init;}) && R.init.empty))+/
{
auto retval = R.init;
}
//An infinite range sliced at [0 .. 0] would likely still not be empty...
else static if(hasSlicing!R && !isInfinite!R)
auto retval = range[0 .. 0];
else
auto retval = takeExactly(range, 0);
//@@@BUG@@@ 7892 prevents this from being done in an out block.
assert(retval.empty);
return retval;
}
//Verify Examples.
unittest
{
assert(takeNone([42, 27, 19]).empty);
assert(takeNone("dlang.org").empty);
assert(takeNone(filter!"true"([42, 27, 19])).empty);
}
unittest
{
import std.metastrings;
string genInput()
{
return "@property bool empty() { return _arr.empty; }" ~
"@property auto front() { return _arr.front; }" ~
"void popFront() { _arr.popFront(); }" ~
"static assert(isInputRange!(typeof(this)));";
}
static struct NormalStruct
{
//Disabled to make sure that the takeExactly version is used.
@disable this();
this(int[] arr) { _arr = arr; }
mixin(genInput());
int[] _arr;
}
static struct SliceStruct
{
@disable this();
this(int[] arr) { _arr = arr; }
mixin(genInput());
auto opSlice(size_t i, size_t j) { return typeof(this)(_arr[i .. j]); }
int[] _arr;
}
static struct InitStruct
{
mixin(genInput());
int[] _arr;
}
static struct TakeNoneStruct
{
this(int[] arr) { _arr = arr; }
@disable this();
mixin(genInput());
auto takeNone() { return typeof(this)(null); }
int[] _arr;
}
static class NormalClass
{
this(int[] arr) {_arr = arr;}
mixin(genInput());
int[] _arr;
}
static class SliceClass
{
this(int[] arr) { _arr = arr; }
mixin(genInput());
auto opSlice(size_t i, size_t j) { return new typeof(this)(_arr[i .. j]); }
int[] _arr;
}
static class TakeNoneClass
{
this(int[] arr) { _arr = arr; }
mixin(genInput());
auto takeNone() { return new typeof(this)(null); }
int[] _arr;
}
foreach(range; TypeTuple!(`[1, 2, 3, 4, 5]`,
`"hello world"`,
`"hello world"w`,
`"hello world"d`,
`SliceStruct([1, 2, 3])`,
//@@@BUG@@@ 8339 forces this to be takeExactly
//`InitStruct([1, 2, 3])`,
`TakeNoneStruct([1, 2, 3])`))
{
mixin(Format!("enum a = takeNone(%s).empty;", range));
assert(a, typeof(range).stringof);
mixin(Format!("assert(takeNone(%s).empty);", range));
mixin(Format!("static assert(is(typeof(%s) == typeof(takeNone(%s))), typeof(%s).stringof);",
range, range, range));
}
foreach(range; TypeTuple!(`NormalStruct([1, 2, 3])`,
`InitStruct([1, 2, 3])`))
{
mixin(Format!("enum a = takeNone(%s).empty;", range));
assert(a, typeof(range).stringof);
mixin(Format!("assert(takeNone(%s).empty);", range));
mixin(Format!("static assert(is(typeof(takeExactly(%s, 0)) == typeof(takeNone(%s))), typeof(%s).stringof);",
range, range, range));
}
//Don't work in CTFE.
auto normal = new NormalClass([1, 2, 3]);
assert(takeNone(normal).empty);
static assert(is(typeof(takeExactly(normal, 0)) == typeof(takeNone(normal))), typeof(normal).stringof);
auto slice = new SliceClass([1, 2, 3]);
assert(takeNone(slice).empty);
static assert(is(SliceClass == typeof(takeNone(slice))), typeof(slice).stringof);
auto taken = new TakeNoneClass([1, 2, 3]);
assert(takeNone(taken).empty);
static assert(is(TakeNoneClass == typeof(takeNone(taken))), typeof(taken).stringof);
auto filtered = filter!"true"([1, 2, 3, 4, 5]);
assert(takeNone(filtered).empty);
//@@@BUG@@@ 8339 and 5941 force this to be takeExactly
//static assert(is(typeof(filtered) == typeof(takeNone(filtered))), typeof(filtered).stringof);
} }