diff --git a/std/range.d b/std/range.d index c9913f92f..1ccaa5d25 100644 --- a/std/range.d +++ b/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); } diff --git a/std/stdio.d b/std/stdio.d index 0defa24dd..6c8b6153e 100644 --- a/std/stdio.d +++ b/std/stdio.d @@ -1809,7 +1809,7 @@ the contents may well have changed). { static import std.file; - /* the C function tmpfile doesn't seem to work, even when called from C */ + /* the C function tmpfile doesn't seem to work, even when called from C */ auto deleteme = testFilename(); auto file = File(deleteme, "w+"); scope(success) std.file.remove(deleteme);