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 enum SearchPolicy
{ {
/**
Searches in a linear fashion.
*/
linear,
/** /**
Searches with a step that is grows linearly (1, 2, 3,...) Searches with a step that is grows linearly (1, 2, 3,...)
leading to a quadratic search schedule (indexes tried are 0, 1, leading to a quadratic search schedule (indexes tried are 0, 1,
@ -8295,13 +8300,14 @@ enum SearchPolicy
} }
/** /**
Represents a sorted random-access range. In addition to the regular Represents a sorted range. In addition to the regular range
range primitives, supports fast operations using binary search. To primitives, supports additional operations that take advantage of the
obtain a $(D SortedRange) from an unsorted range $(D r), use ordering, such as merge and binary search. To obtain a $(D
$(XREF algorithm, sort) which sorts $(D r) in place and returns the SortedRange) from an unsorted range $(D r), use $(XREF algorithm,
corresponding $(D SortedRange). To construct a $(D SortedRange) sort) which sorts $(D r) in place and returns the corresponding $(D
from a range $(D r) that is known to be already sorted, use SortedRange). To construct a $(D SortedRange) from a range $(D r) that
$(LREF assumeSorted) described below. is known to be already sorted, use $(LREF assumeSorted) described
below.
Example: Example:
@ -8335,7 +8341,7 @@ enum SearchPolicy
---- ----
*/ */
struct SortedRange(Range, alias pred = "a < b") struct SortedRange(Range, alias pred = "a < b")
if (isRandomAccessRange!Range && hasLength!Range) if (isInputRange!Range)
{ {
private import std.functional : binaryFun; private import std.functional : binaryFun;
@ -8362,6 +8368,8 @@ if (isRandomAccessRange!Range && hasLength!Range)
import std.conv : text; import std.conv : text;
import std.random : MinstdRand, uniform; import std.random : MinstdRand, uniform;
static if (isRandomAccessRange!Range)
{
// Check the sortedness of the input // Check the sortedness of the input
if (this._input.length < 2) return; if (this._input.length < 2) return;
immutable size_t msb = bsr(this._input.length) + 1; immutable size_t msb = bsr(this._input.length) + 1;
@ -8380,6 +8388,7 @@ if (isRandomAccessRange!Range && hasLength!Range)
} }
} }
} }
}
/// Range primitives. /// Range primitives.
@property bool empty() //const @property bool empty() //const
@ -8388,6 +8397,7 @@ if (isRandomAccessRange!Range && hasLength!Range)
} }
/// Ditto /// Ditto
static if (isForwardRange!Range)
@property auto save() @property auto save()
{ {
// Avoid the constructor // Avoid the constructor
@ -8397,7 +8407,7 @@ if (isRandomAccessRange!Range && hasLength!Range)
} }
/// Ditto /// Ditto
@property auto front() @property auto ref front()
{ {
return _input.front; return _input.front;
} }
@ -8409,7 +8419,9 @@ if (isRandomAccessRange!Range && hasLength!Range)
} }
/// Ditto /// Ditto
@property auto back() static if (isBidirectionalRange!Range)
{
@property auto ref back()
{ {
return _input.back; return _input.back;
} }
@ -8419,8 +8431,10 @@ if (isRandomAccessRange!Range && hasLength!Range)
{ {
_input.popBack(); _input.popBack();
} }
}
/// Ditto /// Ditto
static if (isRandomAccessRange!Range)
auto opIndex(size_t i) auto opIndex(size_t i)
{ {
return _input[i]; return _input[i];
@ -8437,10 +8451,14 @@ if (isRandomAccessRange!Range && hasLength!Range)
} }
/// Ditto /// Ditto
static if (hasLength!Range)
{
@property size_t length() //const @property size_t length() //const
{ {
return _input.length; return _input.length;
} }
alias length opDollar;
}
alias opDollar = length; 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 // of the range and then 1 for the rest, returns the index at
// which the first 1 appears. Used internally by the search routines. // which the first 1 appears. Used internally by the search routines.
private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v) 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; size_t first = 0, count = _input.length;
while (count > 0) while (count > 0)
@ -8475,9 +8493,23 @@ if (isRandomAccessRange!Range && hasLength!Range)
return first; 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 // Specialization for trot and gallop
private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v) 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; if (empty || test(front, v)) return 0;
immutable count = length; immutable count = length;
@ -8509,7 +8541,8 @@ if (isRandomAccessRange!Range && hasLength!Range)
// Specialization for trotBackwards and gallopBackwards // Specialization for trotBackwards and gallopBackwards
private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v) 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; immutable count = length;
if (empty || !test(back, v)) return count; if (empty || !test(back, v)) return count;
@ -8556,20 +8589,26 @@ if (isRandomAccessRange!Range && hasLength!Range)
---- ----
*/ */
auto lowerBound(SearchPolicy sp = SearchPolicy.binarySearch, V)(V value) 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)]; return this[0 .. getTransitionIndex!(sp, geq)(value)];
} }
// upperBound // upperBound
/** /**
This function uses binary search with policy $(D sp) to find the This function searches with policy $(D sp) to find the largest right
largest right subrange on which $(D pred(value, x)) is $(D true) subrange on which $(D pred(value, x)) is $(D true) for all $(D x)
for all $(D x) (e.g., if $(D pred) is "less than", returns the (e.g., if $(D pred) is "less than", returns the portion of the range
portion of the range with elements strictly greater than $(D with elements strictly greater than $(D value)). The search schedule
value)). The search schedule and its complexity are documented in and its complexity are documented in $(LREF SearchPolicy).
$(LREF SearchPolicy). See also STL's
$(WEB sgi.com/tech/stl/lower_bound.html,upper_bound). 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: Example:
---- ----
@ -8578,11 +8617,28 @@ if (isRandomAccessRange!Range && hasLength!Range)
assert(equal(p, [4, 4, 5, 6])); assert(equal(p, [4, 4, 5, 6]));
---- ----
*/ */
auto upperBound(SearchPolicy sp = SearchPolicy.binarySearch, V)(V value) auto upperBound(SearchPolicy sp = isRandomAccessRange!Range
if (isTwoWayCompatible!(predFun, ElementType!Range, V)) ? SearchPolicy.binarySearch
: SearchPolicy.linear,
V)(V value)
if (sp == SearchPolicy.linear
||
isTwoWayCompatible!(predFun, ElementType!Range, V)
&& isRandomAccessRange!Range && sp != SearchPolicy.linear)
{
static if (sp == SearchPolicy.linear)
{
for (; !_input.empty && !predFun(value, _input.front);
_input.popFront())
{
}
return this;
}
else
{ {
return this[getTransitionIndex!(sp, gt)(value) .. length]; return this[getTransitionIndex!(sp, gt)(value) .. length];
} }
}
// equalRange // equalRange
/** /**
@ -8606,7 +8662,8 @@ if (isRandomAccessRange!Range && hasLength!Range)
---- ----
*/ */
auto equalRange(V)(V value) 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; size_t first = 0, count = _input.length;
while (count > 0) while (count > 0)
@ -8662,7 +8719,8 @@ assert(equal(r[2], [ 4, 4, 5, 6 ]));
---- ----
*/ */
auto trisect(V)(V value) 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; size_t first = 0, count = _input.length;
while (count > 0) while (count > 0)
@ -8710,6 +8768,7 @@ sgi.com/tech/stl/binary_search.html, binary_search).
*/ */
bool contains(V)(V value) bool contains(V)(V value)
if (isRandomAccessRange!Range)
{ {
size_t first = 0, count = _input.length; size_t first = 0, count = _input.length;
while (count > 0) while (count > 0)
@ -8838,6 +8897,21 @@ unittest
auto s = assumeSorted(arr); 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 Assumes $(D r) is sorted by predicate $(D pred) and returns the
corresponding $(D SortedRange!(pred, R)) having $(D r) as support. To 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). cost $(BIGOH n), use $(XREF algorithm,isSorted).
*/ */
auto assumeSorted(alias pred = "a < b", R)(R r) auto assumeSorted(alias pred = "a < b", R)(R r)
if (isRandomAccessRange!(Unqual!R)) if (isInputRange!(Unqual!R))
{ {
return SortedRange!(Unqual!R, pred)(r); return SortedRange!(Unqual!R, pred)(r);
} }