Fix Issue 9616 - SortedRange should support all range kinds

This commit is contained in:
Andrei Alexandrescu 2013-02-28 18:27:14 -05:00
parent b553983df8
commit 8366c67d9e
2 changed files with 160 additions and 86 deletions

View file

@ -8249,6 +8249,11 @@ template isTwoWayCompatible(alias fn, T1, T2)
*/
enum SearchPolicy
{
/**
Searches in a linear fashion.
*/
linear,
/**
Searches with a step that is grows linearly (1, 2, 3,...)
leading to a quadratic search schedule (indexes tried are 0, 1,
@ -8295,47 +8300,48 @@ enum SearchPolicy
}
/**
Represents a sorted random-access range. In addition to the regular
range primitives, supports fast operations using binary search. To
obtain a $(D SortedRange) from an unsorted range $(D r), use
$(XREF algorithm, sort) which sorts $(D r) in place and returns the
corresponding $(D SortedRange). To construct a $(D SortedRange)
from a range $(D r) that is known to be already sorted, use
$(LREF assumeSorted) described below.
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 $(D r), use $(XREF algorithm,
sort) which sorts $(D r) in place and returns the corresponding $(D
SortedRange). To construct a $(D SortedRange) from a range $(D r) that
is known to be already sorted, use $(LREF assumeSorted) described
below.
Example:
Example:
----
auto a = [ 1, 2, 3, 42, 52, 64 ];
auto r = assumeSorted(a);
assert(r.contains(3));
assert(!r.contains(32));
auto r1 = sort!"a > b"(a);
assert(r1.contains(3));
assert(!r1.contains(32));
assert(r1.release() == [ 64, 52, 42, 3, 2, 1 ]);
----
----
auto a = [ 1, 2, 3, 42, 52, 64 ];
auto r = assumeSorted(a);
assert(r.contains(3));
assert(!r.contains(32));
auto r1 = sort!"a > b"(a);
assert(r1.contains(3));
assert(!r1.contains(32));
assert(r1.release() == [ 64, 52, 42, 3, 2, 1 ]);
----
$(D SortedRange) could accept ranges weaker than random-access, but it
is unable to provide interesting functionality for them. Therefore,
$(D SortedRange) is currently restricted to random-access ranges.
$(D SortedRange) could accept ranges weaker than random-access, but it
is unable to provide interesting functionality for them. Therefore,
$(D SortedRange) is currently restricted to random-access ranges.
No copy of the original range is ever made. If the underlying range is
changed concurrently with its corresponding $(D SortedRange) in ways
that break its sortedness, $(D SortedRange) will work erratically.
No copy of the original range is ever made. If the underlying range is
changed concurrently with its corresponding $(D SortedRange) in ways
that break its sortedness, $(D SortedRange) will work erratically.
Example:
Example:
----
auto a = [ 1, 2, 3, 42, 52, 64 ];
auto r = assumeSorted(a);
assert(r.contains(42));
swap(a[3], a[5]); // illegal to break sortedness of original range
assert(!r.contains(42)); // passes although it shouldn't
----
----
auto a = [ 1, 2, 3, 42, 52, 64 ];
auto r = assumeSorted(a);
assert(r.contains(42));
swap(a[3], a[5]); // illegal to break sortedness of original range
assert(!r.contains(42)); // passes although it shouldn't
----
*/
struct SortedRange(Range, alias pred = "a < b")
if (isRandomAccessRange!Range && hasLength!Range)
if (isInputRange!Range)
{
private import std.functional : binaryFun;
@ -8362,21 +8368,24 @@ if (isRandomAccessRange!Range && hasLength!Range)
import std.conv : text;
import std.random : MinstdRand, uniform;
// Check the sortedness of the input
if (this._input.length < 2) return;
immutable size_t msb = bsr(this._input.length) + 1;
assert(msb > 0 && msb <= this._input.length);
immutable step = this._input.length / msb;
static MinstdRand gen;
immutable start = uniform(0, step, gen);
auto st = stride(this._input, step);
static if (is(typeof(text(st))))
static if (isRandomAccessRange!Range)
{
assert(isSorted!pred(st), text(st));
}
else
{
assert(isSorted!pred(st));
// Check the sortedness of the input
if (this._input.length < 2) return;
immutable size_t msb = bsr(this._input.length) + 1;
assert(msb > 0 && msb <= this._input.length);
immutable step = this._input.length / msb;
static MinstdRand gen;
immutable start = uniform(0, step, gen);
auto st = stride(this._input, step);
static if (is(typeof(text(st))))
{
assert(isSorted!pred(st), text(st));
}
else
{
assert(isSorted!pred(st));
}
}
}
}
@ -8388,6 +8397,7 @@ if (isRandomAccessRange!Range && hasLength!Range)
}
/// Ditto
static if (isForwardRange!Range)
@property auto save()
{
// Avoid the constructor
@ -8397,7 +8407,7 @@ if (isRandomAccessRange!Range && hasLength!Range)
}
/// Ditto
@property auto front()
@property auto ref front()
{
return _input.front;
}
@ -8409,22 +8419,26 @@ if (isRandomAccessRange!Range && hasLength!Range)
}
/// Ditto
@property auto back()
static if (isBidirectionalRange!Range)
{
return _input.back;
@property auto ref back()
{
return _input.back;
}
/// Ditto
void popBack()
{
_input.popBack();
}
}
/// Ditto
void popBack()
{
_input.popBack();
}
/// Ditto
auto opIndex(size_t i)
{
return _input[i];
}
static if (isRandomAccessRange!Range)
auto opIndex(size_t i)
{
return _input[i];
}
/// Ditto
static if (hasSlicing!Range)
@ -8437,9 +8451,13 @@ if (isRandomAccessRange!Range && hasLength!Range)
}
/// Ditto
@property size_t length() //const
static if (hasLength!Range)
{
return _input.length;
@property size_t length() //const
{
return _input.length;
}
alias length opDollar;
}
alias opDollar = length;
@ -8456,7 +8474,7 @@ if (isRandomAccessRange!Range && hasLength!Range)
// of the range and then 1 for the rest, returns the index at
// which the first 1 appears. Used internally by the search routines.
private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v)
if (sp == SearchPolicy.binarySearch)
if (sp == SearchPolicy.binarySearch && isRandomAccessRange!Range)
{
size_t first = 0, count = _input.length;
while (count > 0)
@ -8475,9 +8493,23 @@ if (isRandomAccessRange!Range && hasLength!Range)
return first;
}
// Specialization for linear
private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v)
if (sp == SearchPolicy.linear)
{
size_t i = 0;
immutable count = _input.length;
for (; i < count; ++i)
{
if (test(_input[i], v)) break;
}
return i;
}
// Specialization for trot and gallop
private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v)
if (sp == SearchPolicy.trot || sp == SearchPolicy.gallop)
if ((sp == SearchPolicy.trot || sp == SearchPolicy.gallop)
&& isRandomAccessRange!Range)
{
if (empty || test(front, v)) return 0;
immutable count = length;
@ -8509,7 +8541,8 @@ if (isRandomAccessRange!Range && hasLength!Range)
// Specialization for trotBackwards and gallopBackwards
private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v)
if (sp == SearchPolicy.trotBackwards || sp == SearchPolicy.gallopBackwards)
if ((sp == SearchPolicy.trotBackwards || sp == SearchPolicy.gallopBackwards)
&& isRandomAccessRange!Range)
{
immutable count = length;
if (empty || !test(back, v)) return count;
@ -8556,32 +8589,55 @@ if (isRandomAccessRange!Range && hasLength!Range)
----
*/
auto lowerBound(SearchPolicy sp = SearchPolicy.binarySearch, V)(V value)
if (isTwoWayCompatible!(predFun, ElementType!Range, V))
if (isTwoWayCompatible!(predFun, ElementType!Range, V)
&& isRandomAccessRange!Range)
{
return this[0 .. getTransitionIndex!(sp, geq)(value)];
}
// upperBound
/**
This function uses binary search with policy $(D sp) to find the
largest right subrange on which $(D pred(value, x)) is $(D true)
for all $(D x) (e.g., if $(D pred) is "less than", returns the
portion of the range with elements strictly greater than $(D
value)). The search schedule and its complexity are documented in
$(LREF SearchPolicy). See also STL's
$(WEB sgi.com/tech/stl/lower_bound.html,upper_bound).
This function searches with policy $(D sp) to find the largest right
subrange on which $(D pred(value, x)) is $(D true) for all $(D x)
(e.g., if $(D pred) is "less than", returns the portion of the range
with elements strictly greater than $(D value)). The search schedule
and its complexity are documented in $(LREF SearchPolicy).
Example:
----
auto a = assumeSorted([ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]);
auto p = a.upperBound(3);
assert(equal(p, [4, 4, 5, 6]));
----
For ranges that do not offer random access, $(D SearchPolicy.linear)
is the only policy allowed (and the default). For random-access
searches, all policies are allowed, and $(D SearchPolicy.binarySearch)
is the default.
See_Also: STL's $(WEB sgi.com/tech/stl/lower_bound.html,upper_bound).
Example:
----
auto a = assumeSorted([ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]);
auto p = a.upperBound(3);
assert(equal(p, [4, 4, 5, 6]));
----
*/
auto upperBound(SearchPolicy sp = SearchPolicy.binarySearch, V)(V value)
if (isTwoWayCompatible!(predFun, ElementType!Range, V))
auto upperBound(SearchPolicy sp = isRandomAccessRange!Range
? SearchPolicy.binarySearch
: SearchPolicy.linear,
V)(V value)
if (sp == SearchPolicy.linear
||
isTwoWayCompatible!(predFun, ElementType!Range, V)
&& isRandomAccessRange!Range && sp != SearchPolicy.linear)
{
return this[getTransitionIndex!(sp, gt)(value) .. length];
static if (sp == SearchPolicy.linear)
{
for (; !_input.empty && !predFun(value, _input.front);
_input.popFront())
{
}
return this;
}
else
{
return this[getTransitionIndex!(sp, gt)(value) .. length];
}
}
// equalRange
@ -8606,7 +8662,8 @@ if (isRandomAccessRange!Range && hasLength!Range)
----
*/
auto equalRange(V)(V value)
if (isTwoWayCompatible!(predFun, ElementType!Range, V))
if (isTwoWayCompatible!(predFun, ElementType!Range, V)
&& isRandomAccessRange!Range)
{
size_t first = 0, count = _input.length;
while (count > 0)
@ -8662,7 +8719,8 @@ assert(equal(r[2], [ 4, 4, 5, 6 ]));
----
*/
auto trisect(V)(V value)
if (isTwoWayCompatible!(predFun, ElementType!Range, V))
if (isTwoWayCompatible!(predFun, ElementType!Range, V)
&& isRandomAccessRange!Range)
{
size_t first = 0, count = _input.length;
while (count > 0)
@ -8710,6 +8768,7 @@ sgi.com/tech/stl/binary_search.html, binary_search).
*/
bool contains(V)(V value)
if (isRandomAccessRange!Range)
{
size_t first = 0, count = _input.length;
while (count > 0)
@ -8838,6 +8897,21 @@ unittest
auto s = assumeSorted(arr);
}
// Test on an input range
unittest
{
import std.file, std.path;
auto name = join(tempDir(), "test.std.range." ~ text(__LINE__));
auto f = File(name, "w");
// write a sorted range of lines to the file
f.write("abc\ndef\nghi\njkl");
f.close();
f.open(name);
auto r = assumeSorted(f.byLine());
auto r1 = r.upperBound("def");
assert(r1.front == "ghi");
}
/**
Assumes $(D r) is sorted by predicate $(D pred) and returns the
corresponding $(D SortedRange!(pred, R)) having $(D r) as support. To
@ -8852,7 +8926,7 @@ almost-sorted range is likely to pass it). To check for sortedness at
cost $(BIGOH n), use $(XREF algorithm,isSorted).
*/
auto assumeSorted(alias pred = "a < b", R)(R r)
if (isRandomAccessRange!(Unqual!R))
if (isInputRange!(Unqual!R))
{
return SortedRange!(Unqual!R, pred)(r);
}