Fix Issue 15230 - Inconsistent std.range.SortedRange predicate checks

This commit is contained in:
Berni 2019-09-29 16:57:39 +02:00
parent 4b125d87c0
commit 58c3a75738
2 changed files with 98 additions and 49 deletions

View file

@ -1954,7 +1954,7 @@ if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range ||
// This should get smaller with time. On occasion it may go larger, but only // This should get smaller with time. On occasion it may go larger, but only
// if there's thorough justification. // if there's thorough justification.
debug enum uint watermark = 1676280; debug enum uint watermark = 1676220;
else enum uint watermark = 1676220; else enum uint watermark = 1676220;
import std.conv; import std.conv;

View file

@ -10564,16 +10564,56 @@ enum SearchPolicy
} }
/** /**
Represents a sorted range. In addition to the regular range Options for $(LREF SortedRange) ranges (below).
primitives, supports additional operations that take advantage of the
ordering, such as merge and binary search. To obtain a $(D
SortedRange) from an unsorted range `r`, use
$(REF sort, std,algorithm,sorting) which sorts `r` in place and returns the
corresponding `SortedRange`. To construct a `SortedRange` from a range
`r` that is known to be already sorted, use $(LREF assumeSorted) described
below.
*/ */
struct SortedRange(Range, alias pred = "a < b") enum SortedRangeOptions
{
/**
Assume, that the range is sorted without checking.
*/
assumeSorted,
/**
All elements of the range are checked to be sorted.
The check is performed in O(n) time.
*/
checkStrictly,
/**
Some elements of the range are checked to be sorted.
For ranges with random order, this will almost surely
detect, that it is not sorted. For almost sorted ranges
it's more likely to fail. The checked elements are choosen
in a deterministic manner, which makes this check reproducable.
The check is performed in O(log(n)) time.
*/
checkRoughly,
}
///
@safe pure unittest
{
// create a SortedRange, that's checked strictly
SortedRange!(int[],"a < b", SortedRangeOptions.checkStrictly)([ 1, 3, 5, 7, 9 ]);
}
/**
Represents a sorted range. In addition to the regular range
primitives, supports additional operations that take advantage of the
ordering, such as merge and binary search. To obtain a $(D
SortedRange) from an unsorted range `r`, use
$(REF sort, std,algorithm,sorting) which sorts `r` in place and returns the
corresponding `SortedRange`. To construct a `SortedRange` from a range
`r` that is known to be already sorted, use $(LREF assumeSorted).
Params:
pred: The predicate used to define the sortedness
opt: Controls how strongly the range is checked for sortedness.
Will only be used for `RandomAccessRanges`.
Will not be used in CTFE.
*/
struct SortedRange(Range, alias pred = "a < b",
SortedRangeOptions opt = SortedRangeOptions.assumeSorted)
if (isInputRange!Range && !isInstanceOf!(SortedRange, Range)) if (isInputRange!Range && !isInstanceOf!(SortedRange, Range))
{ {
import std.functional : binaryFun; import std.functional : binaryFun;
@ -10592,36 +10632,53 @@ if (isInputRange!Range && !isInstanceOf!(SortedRange, Range))
// Undocummented because a clearer way to invoke is by calling // Undocummented because a clearer way to invoke is by calling
// assumeSorted. // assumeSorted.
this(Range input) this(Range input)
out
{
// moved out of the body as a workaround for Issue 12661
dbgVerifySorted();
}
do
{ {
static if (opt == SortedRangeOptions.checkRoughly)
{
roughlyVerifySorted(input);
}
static if (opt == SortedRangeOptions.checkStrictly)
{
strictlyVerifySorted(input);
}
this._input = input; this._input = input;
} }
// Assertion only. // Assertion only.
private void dbgVerifySorted() private void roughlyVerifySorted(Range r)
{ {
if (!__ctfe) if (!__ctfe)
debug
{ {
static if (isRandomAccessRange!Range && hasLength!Range) static if (isRandomAccessRange!Range && hasLength!Range)
{ {
import core.bitop : bsr; import core.bitop : bsr;
import std.algorithm.sorting : isSorted; import std.algorithm.sorting : isSorted;
import std.exception : enforce;
// Check the sortedness of the input // Check the sortedness of the input
if (this._input.length < 2) return; if (r.length < 2) return;
immutable size_t msb = bsr(this._input.length) + 1; immutable size_t msb = bsr(r.length) + 1;
assert(msb > 0 && msb <= this._input.length); assert(msb > 0 && msb <= r.length);
immutable step = this._input.length / msb; immutable step = r.length / msb;
auto st = stride(this._input, step); auto st = stride(r, step);
assert(isSorted!pred(st), "Range is not sorted"); enforce(isSorted!pred(st), "Range is not sorted");
}
}
}
// Assertion only.
private void strictlyVerifySorted(Range r)
{
if (!__ctfe)
{
static if (isRandomAccessRange!Range && hasLength!Range)
{
import std.algorithm.sorting : isSorted;
import std.exception : enforce;
enforce(isSorted!pred(r), "Range is not sorted");
} }
} }
} }
@ -11025,11 +11082,12 @@ sorting relation.
} }
/// ditto /// ditto
template SortedRange(Range, alias pred = "a < b") template SortedRange(Range, alias pred = "a < b",
SortedRangeOptions opt = SortedRangeOptions.assumeSorted)
if (isInstanceOf!(SortedRange, Range)) if (isInstanceOf!(SortedRange, Range))
{ {
// Avoid nesting SortedRange types (see Issue 18933); // Avoid nesting SortedRange types (see Issue 18933);
alias SortedRange = SortedRange!(Unqual!(typeof(Range._input)), pred); alias SortedRange = SortedRange!(Unqual!(typeof(Range._input)), pred, opt);
} }
/// ///
@ -11065,6 +11123,18 @@ that break its sorted-ness, `SortedRange` will work erratically.
assert(!r.contains(42)); // passes although it shouldn't assert(!r.contains(42)); // passes although it shouldn't
} }
@safe unittest
{
import std.exception : assertThrown, assertNotThrown;
assertNotThrown(SortedRange!(int[])([ 1, 3, 10, 5, 7 ]));
assertThrown(SortedRange!(int[],"a < b", SortedRangeOptions.checkStrictly)([ 1, 3, 10, 5, 7 ]));
// these two checks are implementation depended
assertNotThrown(SortedRange!(int[],"a < b", SortedRangeOptions.checkRoughly)([ 1, 3, 10, 5, 12, 2 ]));
assertThrown(SortedRange!(int[],"a < b", SortedRangeOptions.checkRoughly)([ 1, 3, 10, 5, 2, 12 ]));
}
@safe unittest @safe unittest
{ {
import std.algorithm.comparison : equal; import std.algorithm.comparison : equal;
@ -11199,15 +11269,8 @@ that break its sorted-ness, `SortedRange` will work erratically.
/** /**
Assumes `r` is sorted by predicate `pred` and returns the Assumes `r` is sorted by predicate `pred` and returns the
corresponding $(D SortedRange!(pred, R)) having `r` as support. To corresponding $(D SortedRange!(pred, R)) having `r` as support.
keep the checking costs low, the cost is $(BIGOH 1) in release mode To check for sorted-ness at
(no checks for sorted-ness are performed). In debug mode, a few random
elements of `r` are checked for sorted-ness. The size of the sample
is proportional $(BIGOH log(r.length)). That way, checking has no
effect on the complexity of subsequent operations specific to sorted
ranges (such as binary search). The probability of an arbitrary
unsorted range failing the test is very high (however, an
almost-sorted range is likely to pass it). To check for sorted-ness at
cost $(BIGOH n), use $(REF isSorted, std,algorithm,sorting). cost $(BIGOH n), use $(REF isSorted, std,algorithm,sorting).
*/ */
auto assumeSorted(alias pred = "a < b", R)(R r) auto assumeSorted(alias pred = "a < b", R)(R r)
@ -11299,20 +11362,6 @@ if (isInputRange!(Unqual!R))
r = assumeSorted(a); r = assumeSorted(a);
} }
@system unittest
{
bool ok = true;
try
{
auto r2 = assumeSorted([ 677, 345, 34, 7, 5 ]);
debug ok = false;
}
catch (Throwable)
{
}
assert(ok);
}
// issue 15003 // issue 15003
@nogc @safe unittest @nogc @safe unittest
{ {