mirror of
https://github.com/dlang/phobos.git
synced 2025-05-03 00:20:26 +03:00
Fix Issue 16628 - std.algorithm.equal for known empty or infinite ranges
* If one of the ranges has `Range.empty == true`, we can define `equal` even when each `front` is not comparable. * If one range is infinite and the other defines `length`, return false. * If both are infinite, cause a compile-time error.
This commit is contained in:
parent
336f5c47d2
commit
2a45a145e8
1 changed files with 66 additions and 17 deletions
|
@ -710,28 +710,52 @@ Compares two ranges for equality, as defined by predicate $(D pred)
|
||||||
*/
|
*/
|
||||||
template equal(alias pred = "a == b")
|
template equal(alias pred = "a == b")
|
||||||
{
|
{
|
||||||
|
/// Internal template - returns true if `R.empty == true`.
|
||||||
|
enum isEmptyRange(R) =
|
||||||
|
isInputRange!R && __traits(compiles, {static assert(R.empty);});
|
||||||
|
|
||||||
|
enum hasFixedLength(T) = hasLength!T || isNarrowString!T;
|
||||||
|
|
||||||
/++
|
/++
|
||||||
This function compares to ranges for equality. The ranges may have
|
Compares two ranges for equality. The ranges may have
|
||||||
different element types, as long as $(D pred(a, b)) evaluates to $(D bool)
|
different element types, as long as $(D pred(r1.front, r2.front))
|
||||||
for $(D a) in $(D r1) and $(D b) in $(D r2).
|
evaluates to $(D bool).
|
||||||
Performs $(BIGOH min(r1.length, r2.length)) evaluations of $(D pred).
|
Performs $(BIGOH min(r1.length, r2.length)) evaluations of $(D pred).
|
||||||
|
|
||||||
|
Alternatively, if `Range1` or `Range2` satisfy $(LREF isEmptyRange),
|
||||||
|
`pred(r1.front, r2.front)` will $(B not) be evaluated.
|
||||||
|
|
||||||
Params:
|
Params:
|
||||||
r1 = The first range to be compared.
|
r1 = The first range to be compared.
|
||||||
r2 = The second range to be compared.
|
r2 = The second range to be compared.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
$(D true) if and only if the two ranges compare equal element
|
$(D true) if and only if the two ranges compare _equal element
|
||||||
for element, according to binary predicate $(D pred).
|
for element, according to binary predicate $(D pred).
|
||||||
|
|
||||||
See_Also:
|
See_Also:
|
||||||
$(HTTP sgi.com/tech/stl/_equal.html, STL's _equal)
|
$(HTTP sgi.com/tech/stl/_equal.html, STL's _equal)
|
||||||
+/
|
+/
|
||||||
bool equal(Range1, Range2)(Range1 r1, Range2 r2)
|
bool equal(Range1, Range2)(Range1 r1, Range2 r2)
|
||||||
if (isInputRange!Range1 && isInputRange!Range2 && is(typeof(binaryFun!pred(r1.front, r2.front))))
|
if (isInputRange!Range1 && isInputRange!Range2 &&
|
||||||
|
(is(typeof(binaryFun!pred(r1.front, r2.front))) ||
|
||||||
|
isEmptyRange!Range1 || isEmptyRange!Range2))
|
||||||
{
|
{
|
||||||
//Start by detecting default pred and compatible dynamicarray.
|
static assert(!(isInfinite!Range1 && isInfinite!Range2),
|
||||||
static if (is(typeof(pred) == string) && pred == "a == b" &&
|
"Both ranges are known to be infinite");
|
||||||
|
|
||||||
|
//No pred calls necessary
|
||||||
|
static if (isEmptyRange!Range1 || isEmptyRange!Range2)
|
||||||
|
{
|
||||||
|
return r1.empty && r2.empty;
|
||||||
|
}
|
||||||
|
else static if ((isInfinite!Range1 && hasFixedLength!Range2) ||
|
||||||
|
(hasFixedLength!Range1 && isInfinite!Range2))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//Detect default pred and compatible dynamic array
|
||||||
|
else static if (is(typeof(pred) == string) && pred == "a == b" &&
|
||||||
isArray!Range1 && isArray!Range2 && is(typeof(r1 == r2)))
|
isArray!Range1 && isArray!Range2 && is(typeof(r1 == r2)))
|
||||||
{
|
{
|
||||||
return r1 == r2;
|
return r1 == r2;
|
||||||
|
@ -746,16 +770,12 @@ template equal(alias pred = "a == b")
|
||||||
|
|
||||||
static if (isAutodecodableString!Range1)
|
static if (isAutodecodableString!Range1)
|
||||||
{
|
{
|
||||||
auto codeUnits = r1.byCodeUnit;
|
return equal(r1.byCodeUnit, r2);
|
||||||
alias other = r2;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto codeUnits = r2.byCodeUnit;
|
return equal(r2.byCodeUnit, r1);
|
||||||
alias other = r1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return equal(codeUnits, other);
|
|
||||||
}
|
}
|
||||||
//Try a fast implementation when the ranges have comparable lengths
|
//Try a fast implementation when the ranges have comparable lengths
|
||||||
else static if (hasLength!Range1 && hasLength!Range2 && is(typeof(r1.length == r2.length)))
|
else static if (hasLength!Range1 && hasLength!Range2 && is(typeof(r1.length == r2.length)))
|
||||||
|
@ -765,7 +785,7 @@ template equal(alias pred = "a == b")
|
||||||
if (len1 != len2) return false; //Short circuit return
|
if (len1 != len2) return false; //Short circuit return
|
||||||
|
|
||||||
//Lengths are the same, so we need to do an actual comparison
|
//Lengths are the same, so we need to do an actual comparison
|
||||||
//Good news is we can sqeeze out a bit of performance by not checking if r2 is empty
|
//Good news is we can squeeze out a bit of performance by not checking if r2 is empty
|
||||||
for (; !r1.empty; r1.popFront(), r2.popFront())
|
for (; !r1.empty; r1.popFront(), r2.popFront())
|
||||||
{
|
{
|
||||||
if (!binaryFun!(pred)(r1.front, r2.front)) return false;
|
if (!binaryFun!(pred)(r1.front, r2.front)) return false;
|
||||||
|
@ -780,6 +800,7 @@ template equal(alias pred = "a == b")
|
||||||
if (r2.empty) return false;
|
if (r2.empty) return false;
|
||||||
if (!binaryFun!(pred)(r1.front, r2.front)) return false;
|
if (!binaryFun!(pred)(r1.front, r2.front)) return false;
|
||||||
}
|
}
|
||||||
|
static if (!isInfinite!Range1)
|
||||||
return r2.empty;
|
return r2.empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -880,9 +901,13 @@ range of range (of range...) comparisons.
|
||||||
cir = new ReferenceInputRange!int([1, 2, 8, 1]);
|
cir = new ReferenceInputRange!int([1, 2, 8, 1]);
|
||||||
assert(!equal(cir, cfr));
|
assert(!equal(cir, cfr));
|
||||||
|
|
||||||
//Test with an infinte range
|
//Test with an infinite range
|
||||||
ReferenceInfiniteForwardRange!int ifr = new ReferenceInfiniteForwardRange!int;
|
auto ifr = new ReferenceInfiniteForwardRange!int;
|
||||||
assert(!equal(a, ifr));
|
assert(!equal(a, ifr));
|
||||||
|
assert(!equal(ifr, a));
|
||||||
|
//Test InputRange without length
|
||||||
|
assert(!equal(ifr, cir));
|
||||||
|
assert(!equal(cir, ifr));
|
||||||
}
|
}
|
||||||
|
|
||||||
@safe pure unittest
|
@safe pure unittest
|
||||||
|
@ -897,6 +922,30 @@ range of range (of range...) comparisons.
|
||||||
assert(equal("æøå"d, "æøå".byDchar));
|
assert(equal("æøå"d, "æøå".byDchar));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@safe pure unittest
|
||||||
|
{
|
||||||
|
struct R(T, bool _empty) {
|
||||||
|
enum empty = _empty;
|
||||||
|
@property T front(){assert(0);}
|
||||||
|
void popFront(){assert(0);}
|
||||||
|
}
|
||||||
|
alias I = R!(char, false);
|
||||||
|
// infinite R1 should have compatible elements even with R2.length defined
|
||||||
|
static assert(!__traits(compiles, I().equal([cast(void*)null])));
|
||||||
|
static assert(!__traits(compiles, I().equal(I())));
|
||||||
|
// strings have fixed length so don't compare elements
|
||||||
|
assert(!I().equal("foo"));
|
||||||
|
assert(!"bar".equal(I()));
|
||||||
|
|
||||||
|
alias E = R!(void*, true);
|
||||||
|
assert(E().equal(E()));
|
||||||
|
// incompatible front types should work with E
|
||||||
|
assert(E().equal(""));
|
||||||
|
assert("".equal(E()));
|
||||||
|
assert(!E().equal("foo"));
|
||||||
|
assert(!"bar".equal(E()));
|
||||||
|
}
|
||||||
|
|
||||||
// MaxType
|
// MaxType
|
||||||
private template MaxType(T...)
|
private template MaxType(T...)
|
||||||
if (T.length >= 1)
|
if (T.length >= 1)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue