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,47 +8300,48 @@ 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:
---- ----
auto a = [ 1, 2, 3, 42, 52, 64 ]; auto a = [ 1, 2, 3, 42, 52, 64 ];
auto r = assumeSorted(a); auto r = assumeSorted(a);
assert(r.contains(3)); assert(r.contains(3));
assert(!r.contains(32)); assert(!r.contains(32));
auto r1 = sort!"a > b"(a); auto r1 = sort!"a > b"(a);
assert(r1.contains(3)); assert(r1.contains(3));
assert(!r1.contains(32)); assert(!r1.contains(32));
assert(r1.release() == [ 64, 52, 42, 3, 2, 1 ]); assert(r1.release() == [ 64, 52, 42, 3, 2, 1 ]);
---- ----
$(D SortedRange) could accept ranges weaker than random-access, but it $(D SortedRange) could accept ranges weaker than random-access, but it
is unable to provide interesting functionality for them. Therefore, is unable to provide interesting functionality for them. Therefore,
$(D SortedRange) is currently restricted to random-access ranges. $(D SortedRange) is currently restricted to random-access ranges.
No copy of the original range is ever made. If the underlying range is No copy of the original range is ever made. If the underlying range is
changed concurrently with its corresponding $(D SortedRange) in ways changed concurrently with its corresponding $(D SortedRange) in ways
that break its sortedness, $(D SortedRange) will work erratically. that break its sortedness, $(D SortedRange) will work erratically.
Example: Example:
---- ----
auto a = [ 1, 2, 3, 42, 52, 64 ]; auto a = [ 1, 2, 3, 42, 52, 64 ];
auto r = assumeSorted(a); auto r = assumeSorted(a);
assert(r.contains(42)); assert(r.contains(42));
swap(a[3], a[5]); // illegal to break sortedness of original range swap(a[3], a[5]); // illegal to break sortedness of original range
assert(!r.contains(42)); // passes although it shouldn't assert(!r.contains(42)); // passes although it shouldn't
---- ----
*/ */
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,33 +8589,56 @@ 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).
Example: For ranges that do not offer random access, $(D SearchPolicy.linear)
---- is the only policy allowed (and the default). For random-access
auto a = assumeSorted([ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]); searches, all policies are allowed, and $(D SearchPolicy.binarySearch)
auto p = a.upperBound(3); is the default.
assert(equal(p, [4, 4, 5, 6]));
---- 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) 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);
} }