Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from Oct 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion std/algorithm/sorting.d
Expand Up @@ -1954,7 +1954,7 @@ if (((ss == SwapStrategy.unstable && (hasSwappableElements!Range ||

// This should get smaller with time. On occasion it may go larger, but only
// if there's thorough justification.
debug enum uint watermark = 1676280;
debug enum uint watermark = 1676220;
else enum uint watermark = 1676220;

import std.conv;
Expand Down
145 changes: 97 additions & 48 deletions std/range/package.d
Expand Up @@ -10564,16 +10564,56 @@ enum SearchPolicy
}

/**
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) described
below.
Options for $(LREF SortedRange) ranges (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))
{
import std.functional : binaryFun;
Expand All @@ -10592,36 +10632,53 @@ if (isInputRange!Range && !isInstanceOf!(SortedRange, Range))
// Undocummented because a clearer way to invoke is by calling
// assumeSorted.
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;
}

// Assertion only.
private void dbgVerifySorted()
private void roughlyVerifySorted(Range r)
{
if (!__ctfe)
debug
{
static if (isRandomAccessRange!Range && hasLength!Range)
{
import core.bitop : bsr;
import std.algorithm.sorting : isSorted;
import std.exception : enforce;

// Check the sortedness of the input
if (this._input.length < 2) return;
if (r.length < 2) return;

immutable size_t msb = bsr(r.length) + 1;
assert(msb > 0 && msb <= r.length);
immutable step = r.length / msb;
auto st = stride(r, step);

enforce(isSorted!pred(st), "Range is not sorted");
}
}
}

immutable size_t msb = bsr(this._input.length) + 1;
assert(msb > 0 && msb <= this._input.length);
immutable step = this._input.length / msb;
auto st = stride(this._input, step);
// Assertion only.
private void strictlyVerifySorted(Range r)
{
if (!__ctfe)
{
static if (isRandomAccessRange!Range && hasLength!Range)
{
import std.algorithm.sorting : isSorted;
import std.exception : enforce;

assert(isSorted!pred(st), "Range is not sorted");
enforce(isSorted!pred(r), "Range is not sorted");
}
}
}
Expand Down Expand Up @@ -11025,11 +11082,12 @@ sorting relation.
}

/// ditto
template SortedRange(Range, alias pred = "a < b")
template SortedRange(Range, alias pred = "a < b",
SortedRangeOptions opt = SortedRangeOptions.assumeSorted)
if (isInstanceOf!(SortedRange, Range))
{
// Avoid nesting SortedRange types (see Issue 18933);
alias SortedRange = SortedRange!(Unqual!(typeof(Range._input)), pred);
alias SortedRange = SortedRange!(Unqual!(typeof(Range._input)), pred, opt);
}

///
Expand Down Expand Up @@ -11065,6 +11123,18 @@ that break its sorted-ness, `SortedRange` will work erratically.
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
{
import std.algorithm.comparison : equal;
Expand Down Expand Up @@ -11199,15 +11269,8 @@ that break its sorted-ness, `SortedRange` will work erratically.

/**
Assumes `r` is sorted by predicate `pred` and returns the
corresponding $(D SortedRange!(pred, R)) having `r` as support. To
keep the checking costs low, the cost is $(BIGOH 1) in release mode
(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
corresponding $(D SortedRange!(pred, R)) having `r` as support.
To check for sorted-ness at
cost $(BIGOH n), use $(REF isSorted, std,algorithm,sorting).
*/
auto assumeSorted(alias pred = "a < b", R)(R r)
Expand Down Expand Up @@ -11299,20 +11362,6 @@ if (isInputRange!(Unqual!R))
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
@nogc @safe unittest
{
Expand Down