mirror of
https://github.com/dlang/phobos.git
synced 2025-04-27 13:40:20 +03:00
Fix Issue 9616 - SortedRange should support all range kinds
This commit is contained in:
parent
b553983df8
commit
8366c67d9e
2 changed files with 160 additions and 86 deletions
244
std/range.d
244
std/range.d
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue