| // Written in the D programming language. |
| |
| /** |
| This module defines the notion of a range. Ranges generalize the concept of |
| arrays, lists, or anything that involves sequential access. This abstraction |
| enables the same set of algorithms (see $(MREF std, algorithm)) to be used |
| with a vast variety of different concrete types. For example, |
| a linear search algorithm such as $(REF find, std, algorithm, searching) |
| works not just for arrays, but for linked-lists, input files, |
| incoming network data, etc. |
| |
| Guides: |
| |
| There are many articles available that can bolster understanding ranges: |
| |
| $(UL |
| $(LI Ali Çehreli's $(HTTP ddili.org/ders/d.en/ranges.html, tutorial on _ranges) |
| for the basics of working with and creating range-based code.) |
| $(LI Jonathan M. Davis $(LINK2 http://dconf.org/2015/talks/davis.html, $(I Introduction to Ranges)) |
| talk at DConf 2015 a vivid introduction from its core constructs to practical advice.) |
| $(LI The DLang Tour's $(LINK2 http://tour.dlang.org/tour/en/basics/ranges, chapter on ranges) |
| for an interactive introduction.) |
| $(LI H. S. Teoh's $(LINK2 http://wiki.dlang.org/Component_programming_with_ranges, tutorial on |
| component programming with ranges) for a real-world showcase of the influence |
| of _range-based programming on complex algorithms.) |
| $(LI Andrei Alexandrescu's article |
| $(LINK2 http://www.informit.com/articles/printerfriendly.aspx?p=1407357$(AMP)rll=1, |
| $(I On Iteration)) for conceptual aspect of ranges and the motivation |
| ) |
| ) |
| |
| Submodules: |
| |
| This module has two submodules: |
| |
| The $(MREF std, _range, primitives) submodule |
| provides basic _range functionality. It defines several templates for testing |
| whether a given object is a _range, what kind of _range it is, and provides |
| some common _range operations. |
| |
| The $(MREF std, _range, interfaces) submodule |
| provides object-based interfaces for working with ranges via runtime |
| polymorphism. |
| |
| The remainder of this module provides a rich set of _range creation and |
| composition templates that let you construct new ranges out of existing ranges: |
| |
| |
| $(SCRIPT inhibitQuickIndex = 1;) |
| $(BOOKTABLE , |
| $(TR $(TD $(LREF chain)) |
| $(TD Concatenates several ranges into a single _range. |
| )) |
| $(TR $(TD $(LREF choose)) |
| $(TD Chooses one of two ranges at runtime based on a boolean condition. |
| )) |
| $(TR $(TD $(LREF chooseAmong)) |
| $(TD Chooses one of several ranges at runtime based on an index. |
| )) |
| $(TR $(TD $(LREF chunks)) |
| $(TD Creates a _range that returns fixed-size chunks of the original |
| _range. |
| )) |
| $(TR $(TD $(LREF cycle)) |
| $(TD Creates an infinite _range that repeats the given forward _range |
| indefinitely. Good for implementing circular buffers. |
| )) |
| $(TR $(TD $(LREF drop)) |
| $(TD Creates the _range that results from discarding the first $(I n) |
| elements from the given _range. |
| )) |
| $(TR $(TD $(LREF dropBack)) |
| $(TD Creates the _range that results from discarding the last $(I n) |
| elements from the given _range. |
| )) |
| $(TR $(TD $(LREF dropExactly)) |
| $(TD Creates the _range that results from discarding exactly $(I n) |
| of the first elements from the given _range. |
| )) |
| $(TR $(TD $(LREF dropBackExactly)) |
| $(TD Creates the _range that results from discarding exactly $(I n) |
| of the last elements from the given _range. |
| )) |
| $(TR $(TD $(LREF dropOne)) |
| $(TD Creates the _range that results from discarding |
| the first element from the given _range. |
| )) |
| $(TR $(TD $(D $(LREF dropBackOne))) |
| $(TD Creates the _range that results from discarding |
| the last element from the given _range. |
| )) |
| $(TR $(TD $(LREF enumerate)) |
| $(TD Iterates a _range with an attached index variable. |
| )) |
| $(TR $(TD $(LREF evenChunks)) |
| $(TD Creates a _range that returns a number of chunks of |
| approximately equal length from the original _range. |
| )) |
| $(TR $(TD $(LREF frontTransversal)) |
| $(TD Creates a _range that iterates over the first elements of the |
| given ranges. |
| )) |
| $(TR $(TD $(LREF generate)) |
| $(TD Creates a _range by successive calls to a given function. This |
| allows to create ranges as a single delegate. |
| )) |
| $(TR $(TD $(LREF indexed)) |
| $(TD Creates a _range that offers a view of a given _range as though |
| its elements were reordered according to a given _range of indices. |
| )) |
| $(TR $(TD $(LREF iota)) |
| $(TD Creates a _range consisting of numbers between a starting point |
| and ending point, spaced apart by a given interval. |
| )) |
| $(TR $(TD $(LREF lockstep)) |
| $(TD Iterates $(I n) _ranges in lockstep, for use in a $(D foreach) |
| loop. Similar to $(D zip), except that $(D lockstep) is designed |
| especially for $(D foreach) loops. |
| )) |
| $(TR $(TD $(LREF NullSink)) |
| $(TD An output _range that discards the data it receives. |
| )) |
| $(TR $(TD $(LREF only)) |
| $(TD Creates a _range that iterates over the given arguments. |
| )) |
| $(TR $(TD $(LREF padLeft)) |
| $(TD Pads a _range to a specified length by adding a given element to |
| the front of the _range. Is lazy if the _range has a known length. |
| )) |
| $(TR $(TD $(LREF padRight)) |
| $(TD Lazily pads a _range to a specified length by adding a given element to |
| the back of the _range. |
| )) |
| $(TR $(TD $(LREF radial)) |
| $(TD Given a random-access _range and a starting point, creates a |
| _range that alternately returns the next left and next right element to |
| the starting point. |
| )) |
| $(TR $(TD $(LREF recurrence)) |
| $(TD Creates a forward _range whose values are defined by a |
| mathematical recurrence relation. |
| )) |
| $(TR $(TD $(LREF refRange)) |
| $(TD Pass a _range by reference. Both the original _range and the RefRange |
| will always have the exact same elements. |
| Any operation done on one will affect the other. |
| )) |
| $(TR $(TD $(LREF repeat)) |
| $(TD Creates a _range that consists of a single element repeated $(I n) |
| times, or an infinite _range repeating that element indefinitely. |
| )) |
| $(TR $(TD $(LREF retro)) |
| $(TD Iterates a bidirectional _range backwards. |
| )) |
| $(TR $(TD $(LREF roundRobin)) |
| $(TD Given $(I n) ranges, creates a new _range that return the $(I n) |
| first elements of each _range, in turn, then the second element of each |
| _range, and so on, in a round-robin fashion. |
| )) |
| $(TR $(TD $(LREF sequence)) |
| $(TD Similar to $(D recurrence), except that a random-access _range is |
| created. |
| )) |
| $(COMMENT Explicitly undocumented to delay the release until 2.076 |
| $(TR $(TD $(D $(LREF slide))) |
| $(TD Creates a _range that returns a fixed-size sliding window |
| over the original _range. Unlike chunks, |
| it advances a configurable number of items at a time, |
| not one chunk at a time. |
| )) |
| ) |
| $(TR $(TD $(LREF stride)) |
| $(TD Iterates a _range with stride $(I n). |
| )) |
| $(TR $(TD $(LREF tail)) |
| $(TD Return a _range advanced to within $(D n) elements of the end of |
| the given _range. |
| )) |
| $(TR $(TD $(LREF take)) |
| $(TD Creates a sub-_range consisting of only up to the first $(I n) |
| elements of the given _range. |
| )) |
| $(TR $(TD $(LREF takeExactly)) |
| $(TD Like $(D take), but assumes the given _range actually has $(I n) |
| elements, and therefore also defines the $(D length) property. |
| )) |
| $(TR $(TD $(LREF takeNone)) |
| $(TD Creates a random-access _range consisting of zero elements of the |
| given _range. |
| )) |
| $(TR $(TD $(LREF takeOne)) |
| $(TD Creates a random-access _range consisting of exactly the first |
| element of the given _range. |
| )) |
| $(TR $(TD $(LREF tee)) |
| $(TD Creates a _range that wraps a given _range, forwarding along |
| its elements while also calling a provided function with each element. |
| )) |
| $(TR $(TD $(LREF transposed)) |
| $(TD Transposes a _range of ranges. |
| )) |
| $(TR $(TD $(LREF transversal)) |
| $(TD Creates a _range that iterates over the $(I n)'th elements of the |
| given random-access ranges. |
| )) |
| $(TR $(TD $(LREF zip)) |
| $(TD Given $(I n) _ranges, creates a _range that successively returns a |
| tuple of all the first elements, a tuple of all the second elements, |
| etc. |
| )) |
| ) |
| |
| Sortedness: |
| |
| Ranges whose elements are sorted afford better efficiency with certain |
| operations. For this, the $(LREF assumeSorted) function can be used to |
| construct a $(LREF SortedRange) from a pre-sorted _range. The $(REF |
| sort, std, algorithm, sorting) function also conveniently |
| returns a $(LREF SortedRange). $(LREF SortedRange) objects provide some additional |
| _range operations that take advantage of the fact that the _range is sorted. |
| |
| Source: $(PHOBOSSRC std/_range/_package.d) |
| |
| License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). |
| |
| Authors: $(HTTP erdani.com, Andrei Alexandrescu), David Simcha, Jonathan M Davis, |
| and Jack Stouffer. Credit for some of the ideas in building this module goes |
| to $(HTTP fantascienza.net/leonardo/so/, Leonardo Maffi). |
| */ |
| module std.range; |
| |
| public import std.array; |
| public import std.range.interfaces; |
| public import std.range.primitives; |
| public import std.typecons : Flag, Yes, No; |
| |
| import std.meta; // allSatisfy, staticMap |
| import std.traits; // CommonType, isCallable, isFloatingPoint, isIntegral, |
| // isPointer, isSomeFunction, isStaticArray, Unqual |
| |
| |
| /** |
| Iterates a bidirectional range backwards. The original range can be |
| accessed by using the $(D source) property. Applying retro twice to |
| the same range yields the original range. |
| |
| Params: |
| r = the bidirectional range to iterate backwards |
| |
| Returns: |
| A bidirectional range with length if `r` also provides a length. Or, |
| if `r` is a random access range, then the return value will be random |
| access as well. |
| See_Also: |
| $(REF reverse, std,algorithm,mutation) for mutating the source range directly. |
| */ |
| auto retro(Range)(Range r) |
| if (isBidirectionalRange!(Unqual!Range)) |
| { |
| // Check for retro(retro(r)) and just return r in that case |
| static if (is(typeof(retro(r.source)) == Range)) |
| { |
| return r.source; |
| } |
| else |
| { |
| static struct Result() |
| { |
| private alias R = Unqual!Range; |
| |
| // User code can get and set source, too |
| R source; |
| |
| static if (hasLength!R) |
| { |
| size_t retroIndex(size_t n) |
| { |
| return source.length - n - 1; |
| } |
| } |
| |
| public: |
| alias Source = R; |
| |
| @property bool empty() { return source.empty; } |
| @property auto save() |
| { |
| return Result(source.save); |
| } |
| @property auto ref front() { return source.back; } |
| void popFront() { source.popBack(); } |
| @property auto ref back() { return source.front; } |
| void popBack() { source.popFront(); } |
| |
| static if (is(typeof(source.moveBack()))) |
| { |
| ElementType!R moveFront() |
| { |
| return source.moveBack(); |
| } |
| } |
| |
| static if (is(typeof(source.moveFront()))) |
| { |
| ElementType!R moveBack() |
| { |
| return source.moveFront(); |
| } |
| } |
| |
| static if (hasAssignableElements!R) |
| { |
| @property void front(ElementType!R val) |
| { |
| source.back = val; |
| } |
| |
| @property void back(ElementType!R val) |
| { |
| source.front = val; |
| } |
| } |
| |
| static if (isRandomAccessRange!(R) && hasLength!(R)) |
| { |
| auto ref opIndex(size_t n) { return source[retroIndex(n)]; } |
| |
| static if (hasAssignableElements!R) |
| { |
| void opIndexAssign(ElementType!R val, size_t n) |
| { |
| source[retroIndex(n)] = val; |
| } |
| } |
| |
| static if (is(typeof(source.moveAt(0)))) |
| { |
| ElementType!R moveAt(size_t index) |
| { |
| return source.moveAt(retroIndex(index)); |
| } |
| } |
| |
| static if (hasSlicing!R) |
| typeof(this) opSlice(size_t a, size_t b) |
| { |
| return typeof(this)(source[source.length - b .. source.length - a]); |
| } |
| } |
| |
| static if (hasLength!R) |
| { |
| @property auto length() |
| { |
| return source.length; |
| } |
| |
| alias opDollar = length; |
| } |
| } |
| |
| return Result!()(r); |
| } |
| } |
| |
| |
| /// |
| pure @safe nothrow @nogc unittest |
| { |
| import std.algorithm.comparison : equal; |
| int[5] a = [ 1, 2, 3, 4, 5 ]; |
| int[5] b = [ 5, 4, 3, 2, 1 ]; |
| assert(equal(retro(a[]), b[])); |
| assert(retro(a[]).source is a[]); |
| assert(retro(retro(a[])) is a[]); |
| } |
| |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| static assert(isBidirectionalRange!(typeof(retro("hello")))); |
| int[] a; |
| static assert(is(typeof(a) == typeof(retro(retro(a))))); |
| assert(retro(retro(a)) is a); |
| static assert(isRandomAccessRange!(typeof(retro([1, 2, 3])))); |
| void test(int[] input, int[] witness) |
| { |
| auto r = retro(input); |
| assert(r.front == witness.front); |
| assert(r.back == witness.back); |
| assert(equal(r, witness)); |
| } |
| test([ 1 ], [ 1 ]); |
| test([ 1, 2 ], [ 2, 1 ]); |
| test([ 1, 2, 3 ], [ 3, 2, 1 ]); |
| test([ 1, 2, 3, 4 ], [ 4, 3, 2, 1 ]); |
| test([ 1, 2, 3, 4, 5 ], [ 5, 4, 3, 2, 1 ]); |
| test([ 1, 2, 3, 4, 5, 6 ], [ 6, 5, 4, 3, 2, 1 ]); |
| |
| immutable foo = [1,2,3].idup; |
| auto r = retro(foo); |
| assert(equal(r, [3, 2, 1])); |
| } |
| |
| pure @safe nothrow unittest |
| { |
| import std.internal.test.dummyrange : AllDummyRanges, propagatesRangeType, |
| ReturnBy; |
| |
| foreach (DummyType; AllDummyRanges) |
| { |
| static if (!isBidirectionalRange!DummyType) |
| { |
| static assert(!__traits(compiles, Retro!DummyType)); |
| } |
| else |
| { |
| DummyType dummyRange; |
| dummyRange.reinit(); |
| |
| auto myRetro = retro(dummyRange); |
| static assert(propagatesRangeType!(typeof(myRetro), DummyType)); |
| assert(myRetro.front == 10); |
| assert(myRetro.back == 1); |
| assert(myRetro.moveFront() == 10); |
| assert(myRetro.moveBack() == 1); |
| |
| static if (isRandomAccessRange!DummyType && hasLength!DummyType) |
| { |
| assert(myRetro[0] == myRetro.front); |
| assert(myRetro.moveAt(2) == 8); |
| |
| static if (DummyType.r == ReturnBy.Reference) |
| { |
| { |
| myRetro[9]++; |
| scope(exit) myRetro[9]--; |
| assert(dummyRange[0] == 2); |
| myRetro.front++; |
| scope(exit) myRetro.front--; |
| assert(myRetro.front == 11); |
| myRetro.back++; |
| scope(exit) myRetro.back--; |
| assert(myRetro.back == 3); |
| } |
| |
| { |
| myRetro.front = 0xFF; |
| scope(exit) myRetro.front = 10; |
| assert(dummyRange.back == 0xFF); |
| |
| myRetro.back = 0xBB; |
| scope(exit) myRetro.back = 1; |
| assert(dummyRange.front == 0xBB); |
| |
| myRetro[1] = 11; |
| scope(exit) myRetro[1] = 8; |
| assert(dummyRange[8] == 11); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| pure @safe nothrow @nogc unittest |
| { |
| import std.algorithm.comparison : equal; |
| auto LL = iota(1L, 4L); |
| auto r = retro(LL); |
| long[3] excepted = [3, 2, 1]; |
| assert(equal(r, excepted[])); |
| } |
| |
| // Issue 12662 |
| pure @safe nothrow @nogc unittest |
| { |
| int[3] src = [1,2,3]; |
| int[] data = src[]; |
| foreach_reverse (x; data) {} |
| foreach (x; data.retro) {} |
| } |
| |
| |
| /** |
| Iterates range $(D r) with stride $(D n). If the range is a |
| random-access range, moves by indexing into the range; otherwise, |
| moves by successive calls to $(D popFront). Applying stride twice to |
| the same range results in a stride with a step that is the |
| product of the two applications. It is an error for $(D n) to be 0. |
| |
| Params: |
| r = the input range to stride over |
| n = the number of elements to skip over |
| |
| Returns: |
| At minimum, an input range. The resulting range will adopt the |
| range primitives of the underlying range as long as |
| $(REF hasLength, std,range,primitives) is `true`. |
| */ |
| auto stride(Range)(Range r, size_t n) |
| if (isInputRange!(Unqual!Range)) |
| in |
| { |
| assert(n != 0, "stride cannot have step zero."); |
| } |
| body |
| { |
| import std.algorithm.comparison : min; |
| |
| static if (is(typeof(stride(r.source, n)) == Range)) |
| { |
| // stride(stride(r, n1), n2) is stride(r, n1 * n2) |
| return stride(r.source, r._n * n); |
| } |
| else |
| { |
| static struct Result |
| { |
| private alias R = Unqual!Range; |
| public R source; |
| private size_t _n; |
| |
| // Chop off the slack elements at the end |
| static if (hasLength!R && |
| (isRandomAccessRange!R && hasSlicing!R |
| || isBidirectionalRange!R)) |
| private void eliminateSlackElements() |
| { |
| auto slack = source.length % _n; |
| |
| if (slack) |
| { |
| slack--; |
| } |
| else if (!source.empty) |
| { |
| slack = min(_n, source.length) - 1; |
| } |
| else |
| { |
| slack = 0; |
| } |
| if (!slack) return; |
| static if (isRandomAccessRange!R && hasLength!R && hasSlicing!R) |
| { |
| source = source[0 .. source.length - slack]; |
| } |
| else static if (isBidirectionalRange!R) |
| { |
| foreach (i; 0 .. slack) |
| { |
| source.popBack(); |
| } |
| } |
| } |
| |
| static if (isForwardRange!R) |
| { |
| @property auto save() |
| { |
| return Result(source.save, _n); |
| } |
| } |
| |
| static if (isInfinite!R) |
| { |
| enum bool empty = false; |
| } |
| else |
| { |
| @property bool empty() |
| { |
| return source.empty; |
| } |
| } |
| |
| @property auto ref front() |
| { |
| return source.front; |
| } |
| |
| static if (is(typeof(.moveFront(source)))) |
| { |
| ElementType!R moveFront() |
| { |
| return source.moveFront(); |
| } |
| } |
| |
| static if (hasAssignableElements!R) |
| { |
| @property void front(ElementType!R val) |
| { |
| source.front = val; |
| } |
| } |
| |
| void popFront() |
| { |
| source.popFrontN(_n); |
| } |
| |
| static if (isBidirectionalRange!R && hasLength!R) |
| { |
| void popBack() |
| { |
| popBackN(source, _n); |
| } |
| |
| @property auto ref back() |
| { |
| eliminateSlackElements(); |
| return source.back; |
| } |
| |
| static if (is(typeof(.moveBack(source)))) |
| { |
| ElementType!R moveBack() |
| { |
| eliminateSlackElements(); |
| return source.moveBack(); |
| } |
| } |
| |
| static if (hasAssignableElements!R) |
| { |
| @property void back(ElementType!R val) |
| { |
| eliminateSlackElements(); |
| source.back = val; |
| } |
| } |
| } |
| |
| static if (isRandomAccessRange!R && hasLength!R) |
| { |
| auto ref opIndex(size_t n) |
| { |
| return source[_n * n]; |
| } |
| |
| /** |
| Forwards to $(D moveAt(source, n)). |
| */ |
| static if (is(typeof(source.moveAt(0)))) |
| { |
| ElementType!R moveAt(size_t n) |
| { |
| return source.moveAt(_n * n); |
| } |
| } |
| |
| static if (hasAssignableElements!R) |
| { |
| void opIndexAssign(ElementType!R val, size_t n) |
| { |
| source[_n * n] = val; |
| } |
| } |
| } |
| |
| static if (hasSlicing!R && hasLength!R) |
| typeof(this) opSlice(size_t lower, size_t upper) |
| { |
| assert(upper >= lower && upper <= length); |
| immutable translatedUpper = (upper == 0) ? 0 : |
| (upper * _n - (_n - 1)); |
| immutable translatedLower = min(lower * _n, translatedUpper); |
| |
| assert(translatedLower <= translatedUpper); |
| |
| return typeof(this)(source[translatedLower .. translatedUpper], _n); |
| } |
| |
| static if (hasLength!R) |
| { |
| @property auto length() |
| { |
| return (source.length + _n - 1) / _n; |
| } |
| |
| alias opDollar = length; |
| } |
| } |
| return Result(r, n); |
| } |
| } |
| |
| /// |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| int[] a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]; |
| assert(equal(stride(a, 3), [ 1, 4, 7, 10 ][])); |
| assert(stride(stride(a, 2), 3) == stride(a, 6)); |
| } |
| |
| pure @safe nothrow @nogc unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| int[4] testArr = [1,2,3,4]; |
| static immutable result = [1, 3]; |
| assert(equal(testArr[].stride(2), result)); |
| } |
| |
| debug pure nothrow @system unittest |
| {//check the contract |
| int[4] testArr = [1,2,3,4]; |
| bool passed = false; |
| scope (success) assert(passed); |
| import core.exception : AssertError; |
| //std.exception.assertThrown won't do because it can't infer nothrow |
| // @@@BUG@@@ 12647 |
| try |
| { |
| auto unused = testArr[].stride(0); |
| } |
| catch (AssertError unused) |
| { |
| passed = true; |
| } |
| } |
| |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.internal.test.dummyrange : AllDummyRanges, propagatesRangeType, |
| ReturnBy; |
| |
| static assert(isRandomAccessRange!(typeof(stride([1, 2, 3], 2)))); |
| void test(size_t n, int[] input, int[] witness) |
| { |
| assert(equal(stride(input, n), witness)); |
| } |
| test(1, [], []); |
| int[] arr = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; |
| assert(stride(stride(arr, 2), 3) is stride(arr, 6)); |
| test(1, arr, arr); |
| test(2, arr, [1, 3, 5, 7, 9]); |
| test(3, arr, [1, 4, 7, 10]); |
| test(4, arr, [1, 5, 9]); |
| |
| // Test slicing. |
| auto s1 = stride(arr, 1); |
| assert(equal(s1[1 .. 4], [2, 3, 4])); |
| assert(s1[1 .. 4].length == 3); |
| assert(equal(s1[1 .. 5], [2, 3, 4, 5])); |
| assert(s1[1 .. 5].length == 4); |
| assert(s1[0 .. 0].empty); |
| assert(s1[3 .. 3].empty); |
| // assert(s1[$ .. $].empty); |
| assert(s1[s1.opDollar .. s1.opDollar].empty); |
| |
| auto s2 = stride(arr, 2); |
| assert(equal(s2[0 .. 2], [1,3])); |
| assert(s2[0 .. 2].length == 2); |
| assert(equal(s2[1 .. 5], [3, 5, 7, 9])); |
| assert(s2[1 .. 5].length == 4); |
| assert(s2[0 .. 0].empty); |
| assert(s2[3 .. 3].empty); |
| // assert(s2[$ .. $].empty); |
| assert(s2[s2.opDollar .. s2.opDollar].empty); |
| |
| // Test fix for Bug 5035 |
| auto m = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]; // 3 rows, 4 columns |
| auto col = stride(m, 4); |
| assert(equal(col, [1, 1, 1])); |
| assert(equal(retro(col), [1, 1, 1])); |
| |
| immutable int[] immi = [ 1, 2, 3 ]; |
| static assert(isRandomAccessRange!(typeof(stride(immi, 1)))); |
| |
| // Check for infiniteness propagation. |
| static assert(isInfinite!(typeof(stride(repeat(1), 3)))); |
| |
| foreach (DummyType; AllDummyRanges) |
| { |
| DummyType dummyRange; |
| dummyRange.reinit(); |
| |
| auto myStride = stride(dummyRange, 4); |
| |
| // Should fail if no length and bidirectional b/c there's no way |
| // to know how much slack we have. |
| static if (hasLength!DummyType || !isBidirectionalRange!DummyType) |
| { |
| static assert(propagatesRangeType!(typeof(myStride), DummyType)); |
| } |
| assert(myStride.front == 1); |
| assert(myStride.moveFront() == 1); |
| assert(equal(myStride, [1, 5, 9])); |
| |
| static if (hasLength!DummyType) |
| { |
| assert(myStride.length == 3); |
| } |
| |
| static if (isBidirectionalRange!DummyType && hasLength!DummyType) |
| { |
| assert(myStride.back == 9); |
| assert(myStride.moveBack() == 9); |
| } |
| |
| static if (isRandomAccessRange!DummyType && hasLength!DummyType) |
| { |
| assert(myStride[0] == 1); |
| assert(myStride[1] == 5); |
| assert(myStride.moveAt(1) == 5); |
| assert(myStride[2] == 9); |
| |
| static assert(hasSlicing!(typeof(myStride))); |
| } |
| |
| static if (DummyType.r == ReturnBy.Reference) |
| { |
| // Make sure reference is propagated. |
| |
| { |
| myStride.front++; |
| scope(exit) myStride.front--; |
| assert(dummyRange.front == 2); |
| } |
| { |
| myStride.front = 4; |
| scope(exit) myStride.front = 1; |
| assert(dummyRange.front == 4); |
| } |
| |
| static if (isBidirectionalRange!DummyType && hasLength!DummyType) |
| { |
| { |
| myStride.back++; |
| scope(exit) myStride.back--; |
| assert(myStride.back == 10); |
| } |
| { |
| myStride.back = 111; |
| scope(exit) myStride.back = 9; |
| assert(myStride.back == 111); |
| } |
| |
| static if (isRandomAccessRange!DummyType) |
| { |
| { |
| myStride[1]++; |
| scope(exit) myStride[1]--; |
| assert(dummyRange[4] == 6); |
| } |
| { |
| myStride[1] = 55; |
| scope(exit) myStride[1] = 5; |
| assert(dummyRange[4] == 55); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| auto LL = iota(1L, 10L); |
| auto s = stride(LL, 3); |
| assert(equal(s, [1L, 4L, 7L])); |
| } |
| |
| /** |
| Spans multiple ranges in sequence. The function $(D chain) takes any |
| number of ranges and returns a $(D Chain!(R1, R2,...)) object. The |
| ranges may be different, but they must have the same element type. The |
| result is a range that offers the $(D front), $(D popFront), and $(D |
| empty) primitives. If all input ranges offer random access and $(D |
| length), $(D Chain) offers them as well. |
| |
| If only one range is offered to $(D Chain) or $(D chain), the $(D |
| Chain) type exits the picture by aliasing itself directly to that |
| range's type. |
| |
| Params: |
| rs = the input ranges to chain together |
| |
| Returns: |
| An input range at minimum. If all of the ranges in `rs` provide |
| a range primitive, the returned range will also provide that range |
| primitive. |
| |
| See_Also: $(LREF only) to chain values to a range |
| */ |
| auto chain(Ranges...)(Ranges rs) |
| if (Ranges.length > 0 && |
| allSatisfy!(isInputRange, staticMap!(Unqual, Ranges)) && |
| !is(CommonType!(staticMap!(ElementType, staticMap!(Unqual, Ranges))) == void)) |
| { |
| static if (Ranges.length == 1) |
| { |
| return rs[0]; |
| } |
| else |
| { |
| static struct Result |
| { |
| private: |
| alias R = staticMap!(Unqual, Ranges); |
| alias RvalueElementType = CommonType!(staticMap!(.ElementType, R)); |
| private template sameET(A) |
| { |
| enum sameET = is(.ElementType!A == RvalueElementType); |
| } |
| |
| enum bool allSameType = allSatisfy!(sameET, R); |
| |
| // This doesn't work yet |
| static if (allSameType) |
| { |
| alias ElementType = ref RvalueElementType; |
| } |
| else |
| { |
| alias ElementType = RvalueElementType; |
| } |
| static if (allSameType && allSatisfy!(hasLvalueElements, R)) |
| { |
| static ref RvalueElementType fixRef(ref RvalueElementType val) |
| { |
| return val; |
| } |
| } |
| else |
| { |
| static RvalueElementType fixRef(RvalueElementType val) |
| { |
| return val; |
| } |
| } |
| |
| // This is the entire state |
| R source; |
| // TODO: use a vtable (or more) instead of linear iteration |
| |
| public: |
| this(R input) |
| { |
| foreach (i, v; input) |
| { |
| source[i] = v; |
| } |
| } |
| |
| import std.meta : anySatisfy; |
| |
| static if (anySatisfy!(isInfinite, R)) |
| { |
| // Propagate infiniteness. |
| enum bool empty = false; |
| } |
| else |
| { |
| @property bool empty() |
| { |
| foreach (i, Unused; R) |
| { |
| if (!source[i].empty) return false; |
| } |
| return true; |
| } |
| } |
| |
| static if (allSatisfy!(isForwardRange, R)) |
| @property auto save() |
| { |
| typeof(this) result = this; |
| foreach (i, Unused; R) |
| { |
| result.source[i] = result.source[i].save; |
| } |
| return result; |
| } |
| |
| void popFront() |
| { |
| foreach (i, Unused; R) |
| { |
| if (source[i].empty) continue; |
| source[i].popFront(); |
| return; |
| } |
| } |
| |
| @property auto ref front() |
| { |
| foreach (i, Unused; R) |
| { |
| if (source[i].empty) continue; |
| return fixRef(source[i].front); |
| } |
| assert(false); |
| } |
| |
| static if (allSameType && allSatisfy!(hasAssignableElements, R)) |
| { |
| // @@@BUG@@@ |
| //@property void front(T)(T v) if (is(T : RvalueElementType)) |
| |
| @property void front(RvalueElementType v) |
| { |
| foreach (i, Unused; R) |
| { |
| if (source[i].empty) continue; |
| source[i].front = v; |
| return; |
| } |
| assert(false); |
| } |
| } |
| |
| static if (allSatisfy!(hasMobileElements, R)) |
| { |
| RvalueElementType moveFront() |
| { |
| foreach (i, Unused; R) |
| { |
| if (source[i].empty) continue; |
| return source[i].moveFront(); |
| } |
| assert(false); |
| } |
| } |
| |
| static if (allSatisfy!(isBidirectionalRange, R)) |
| { |
| @property auto ref back() |
| { |
| foreach_reverse (i, Unused; R) |
| { |
| if (source[i].empty) continue; |
| return fixRef(source[i].back); |
| } |
| assert(false); |
| } |
| |
| void popBack() |
| { |
| foreach_reverse (i, Unused; R) |
| { |
| if (source[i].empty) continue; |
| source[i].popBack(); |
| return; |
| } |
| } |
| |
| static if (allSatisfy!(hasMobileElements, R)) |
| { |
| RvalueElementType moveBack() |
| { |
| foreach_reverse (i, Unused; R) |
| { |
| if (source[i].empty) continue; |
| return source[i].moveBack(); |
| } |
| assert(false); |
| } |
| } |
| |
| static if (allSameType && allSatisfy!(hasAssignableElements, R)) |
| { |
| @property void back(RvalueElementType v) |
| { |
| foreach_reverse (i, Unused; R) |
| { |
| if (source[i].empty) continue; |
| source[i].back = v; |
| return; |
| } |
| assert(false); |
| } |
| } |
| } |
| |
| static if (allSatisfy!(hasLength, R)) |
| { |
| @property size_t length() |
| { |
| size_t result; |
| foreach (i, Unused; R) |
| { |
| result += source[i].length; |
| } |
| return result; |
| } |
| |
| alias opDollar = length; |
| } |
| |
| static if (allSatisfy!(isRandomAccessRange, R)) |
| { |
| auto ref opIndex(size_t index) |
| { |
| foreach (i, Range; R) |
| { |
| static if (isInfinite!(Range)) |
| { |
| return source[i][index]; |
| } |
| else |
| { |
| immutable length = source[i].length; |
| if (index < length) return fixRef(source[i][index]); |
| index -= length; |
| } |
| } |
| assert(false); |
| } |
| |
| static if (allSatisfy!(hasMobileElements, R)) |
| { |
| RvalueElementType moveAt(size_t index) |
| { |
| foreach (i, Range; R) |
| { |
| static if (isInfinite!(Range)) |
| { |
| return source[i].moveAt(index); |
| } |
| else |
| { |
| immutable length = source[i].length; |
| if (index < length) return source[i].moveAt(index); |
| index -= length; |
| } |
| } |
| assert(false); |
| } |
| } |
| |
| static if (allSameType && allSatisfy!(hasAssignableElements, R)) |
| void opIndexAssign(ElementType v, size_t index) |
| { |
| foreach (i, Range; R) |
| { |
| static if (isInfinite!(Range)) |
| { |
| source[i][index] = v; |
| } |
| else |
| { |
| immutable length = source[i].length; |
| if (index < length) |
| { |
| source[i][index] = v; |
| return; |
| } |
| index -= length; |
| } |
| } |
| assert(false); |
| } |
| } |
| |
| static if (allSatisfy!(hasLength, R) && allSatisfy!(hasSlicing, R)) |
| auto opSlice(size_t begin, size_t end) |
| { |
| auto result = this; |
| foreach (i, Unused; R) |
| { |
| immutable len = result.source[i].length; |
| if (len < begin) |
| { |
| result.source[i] = result.source[i] |
| [len .. len]; |
| begin -= len; |
| } |
| else |
| { |
| result.source[i] = result.source[i] |
| [begin .. len]; |
| break; |
| } |
| } |
| auto cut = length; |
| cut = cut <= end ? 0 : cut - end; |
| foreach_reverse (i, Unused; R) |
| { |
| immutable len = result.source[i].length; |
| if (cut > len) |
| { |
| result.source[i] = result.source[i] |
| [0 .. 0]; |
| cut -= len; |
| } |
| else |
| { |
| result.source[i] = result.source[i] |
| [0 .. len - cut]; |
| break; |
| } |
| } |
| return result; |
| } |
| } |
| return Result(rs); |
| } |
| } |
| |
| /// |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| int[] arr1 = [ 1, 2, 3, 4 ]; |
| int[] arr2 = [ 5, 6 ]; |
| int[] arr3 = [ 7 ]; |
| auto s = chain(arr1, arr2, arr3); |
| assert(s.length == 7); |
| assert(s[5] == 6); |
| assert(equal(s, [1, 2, 3, 4, 5, 6, 7][])); |
| } |
| |
| /** |
| * Range primitives are carried over to the returned range if |
| * all of the ranges provide them |
| */ |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.sorting : sort; |
| |
| int[] arr1 = [5, 2, 8]; |
| int[] arr2 = [3, 7, 9]; |
| int[] arr3 = [1, 4, 6]; |
| |
| // in-place sorting across all of the arrays |
| auto s = arr1.chain(arr2, arr3).sort; |
| |
| assert(s.equal([1, 2, 3, 4, 5, 6, 7, 8, 9])); |
| assert(arr1.equal([1, 2, 3])); |
| assert(arr2.equal([4, 5, 6])); |
| assert(arr3.equal([7, 8, 9])); |
| } |
| |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.internal.test.dummyrange : AllDummyRanges, dummyLength, |
| propagatesRangeType; |
| |
| { |
| int[] arr1 = [ 1, 2, 3, 4 ]; |
| int[] arr2 = [ 5, 6 ]; |
| int[] arr3 = [ 7 ]; |
| int[] witness = [ 1, 2, 3, 4, 5, 6, 7 ]; |
| auto s1 = chain(arr1); |
| static assert(isRandomAccessRange!(typeof(s1))); |
| auto s2 = chain(arr1, arr2); |
| static assert(isBidirectionalRange!(typeof(s2))); |
| static assert(isRandomAccessRange!(typeof(s2))); |
| s2.front = 1; |
| auto s = chain(arr1, arr2, arr3); |
| assert(s[5] == 6); |
| assert(equal(s, witness)); |
| assert(s[5] == 6); |
| } |
| { |
| int[] arr1 = [ 1, 2, 3, 4 ]; |
| int[] witness = [ 1, 2, 3, 4 ]; |
| assert(equal(chain(arr1), witness)); |
| } |
| { |
| uint[] foo = [1,2,3,4,5]; |
| uint[] bar = [1,2,3,4,5]; |
| auto c = chain(foo, bar); |
| c[3] = 42; |
| assert(c[3] == 42); |
| assert(c.moveFront() == 1); |
| assert(c.moveBack() == 5); |
| assert(c.moveAt(4) == 5); |
| assert(c.moveAt(5) == 1); |
| } |
| |
| // Make sure bug 3311 is fixed. ChainImpl should compile even if not all |
| // elements are mutable. |
| assert(equal(chain(iota(0, 3), iota(0, 3)), [0, 1, 2, 0, 1, 2])); |
| |
| // Test the case where infinite ranges are present. |
| auto inf = chain([0,1,2][], cycle([4,5,6][]), [7,8,9][]); // infinite range |
| assert(inf[0] == 0); |
| assert(inf[3] == 4); |
| assert(inf[6] == 4); |
| assert(inf[7] == 5); |
| static assert(isInfinite!(typeof(inf))); |
| |
| immutable int[] immi = [ 1, 2, 3 ]; |
| immutable float[] immf = [ 1, 2, 3 ]; |
| static assert(is(typeof(chain(immi, immf)))); |
| |
| // Check that chain at least instantiates and compiles with every possible |
| // pair of DummyRange types, in either order. |
| |
| foreach (DummyType1; AllDummyRanges) |
| { |
| DummyType1 dummy1; |
| foreach (DummyType2; AllDummyRanges) |
| { |
| DummyType2 dummy2; |
| auto myChain = chain(dummy1, dummy2); |
| |
| static assert( |
| propagatesRangeType!(typeof(myChain), DummyType1, DummyType2) |
| ); |
| |
| assert(myChain.front == 1); |
| foreach (i; 0 .. dummyLength) |
| { |
| myChain.popFront(); |
| } |
| assert(myChain.front == 1); |
| |
| static if (isBidirectionalRange!DummyType1 && |
| isBidirectionalRange!DummyType2) { |
| assert(myChain.back == 10); |
| } |
| |
| static if (isRandomAccessRange!DummyType1 && |
| isRandomAccessRange!DummyType2) { |
| assert(myChain[0] == 1); |
| } |
| |
| static if (hasLvalueElements!DummyType1 && hasLvalueElements!DummyType2) |
| { |
| static assert(hasLvalueElements!(typeof(myChain))); |
| } |
| else |
| { |
| static assert(!hasLvalueElements!(typeof(myChain))); |
| } |
| } |
| } |
| } |
| |
| pure @safe nothrow @nogc unittest |
| { |
| class Foo{} |
| immutable(Foo)[] a; |
| immutable(Foo)[] b; |
| assert(chain(a, b).empty); |
| } |
| |
| /** |
| Choose one of two ranges at runtime depending on a Boolean condition. |
| |
| The ranges may be different, but they must have compatible element types (i.e. |
| $(D CommonType) must exist for the two element types). The result is a range |
| that offers the weakest capabilities of the two (e.g. $(D ForwardRange) if $(D |
| R1) is a random-access range and $(D R2) is a forward range). |
| |
| Params: |
| condition = which range to choose: $(D r1) if $(D true), $(D r2) otherwise |
| r1 = the "true" range |
| r2 = the "false" range |
| |
| Returns: |
| A range type dependent on $(D R1) and $(D R2). |
| |
| Bugs: |
| $(BUGZILLA 14660) |
| */ |
| auto choose(R1, R2)(bool condition, R1 r1, R2 r2) |
| if (isInputRange!(Unqual!R1) && isInputRange!(Unqual!R2) && |
| !is(CommonType!(ElementType!(Unqual!R1), ElementType!(Unqual!R2)) == void)) |
| { |
| static struct Result |
| { |
| import std.algorithm.comparison : max; |
| import std.algorithm.internal : addressOf; |
| import std.traits : hasElaborateCopyConstructor, hasElaborateDestructor; |
| |
| private union |
| { |
| void[max(R1.sizeof, R2.sizeof)] buffer = void; |
| void* forAlignmentOnly = void; |
| } |
| private bool condition; |
| private @property ref R1 r1() |
| { |
| assert(condition); |
| return *cast(R1*) buffer.ptr; |
| } |
| private @property ref R2 r2() |
| { |
| assert(!condition); |
| return *cast(R2*) buffer.ptr; |
| } |
| |
| this(bool condition, R1 r1, R2 r2) |
| { |
| this.condition = condition; |
| import std.conv : emplace; |
| if (condition) emplace(addressOf(this.r1), r1); |
| else emplace(addressOf(this.r2), r2); |
| } |
| |
| // Carefully defined postblit to postblit the appropriate range |
| static if (hasElaborateCopyConstructor!R1 |
| || hasElaborateCopyConstructor!R2) |
| this(this) |
| { |
| if (condition) |
| { |
| static if (hasElaborateCopyConstructor!R1) r1.__postblit(); |
| } |
| else |
| { |
| static if (hasElaborateCopyConstructor!R2) r2.__postblit(); |
| } |
| } |
| |
| static if (hasElaborateDestructor!R1 || hasElaborateDestructor!R2) |
| ~this() |
| { |
| if (condition) destroy(r1); |
| else destroy(r2); |
| } |
| |
| static if (isInfinite!R1 && isInfinite!R2) |
| // Propagate infiniteness. |
| enum bool empty = false; |
| else |
| @property bool empty() |
| { |
| return condition ? r1.empty : r2.empty; |
| } |
| |
| @property auto ref front() |
| { |
| return condition ? r1.front : r2.front; |
| } |
| |
| void popFront() |
| { |
| return condition ? r1.popFront : r2.popFront; |
| } |
| |
| static if (isForwardRange!R1 && isForwardRange!R2) |
| @property auto save() |
| { |
| auto result = this; |
| if (condition) r1 = r1.save; |
| else r2 = r2.save; |
| return result; |
| } |
| |
| @property void front(T)(T v) |
| if (is(typeof({ r1.front = v; r2.front = v; }))) |
| { |
| if (condition) r1.front = v; else r2.front = v; |
| } |
| |
| static if (hasMobileElements!R1 && hasMobileElements!R2) |
| auto moveFront() |
| { |
| return condition ? r1.moveFront : r2.moveFront; |
| } |
| |
| static if (isBidirectionalRange!R1 && isBidirectionalRange!R2) |
| { |
| @property auto ref back() |
| { |
| return condition ? r1.back : r2.back; |
| } |
| |
| void popBack() |
| { |
| return condition ? r1.popBack : r2.popBack; |
| } |
| |
| static if (hasMobileElements!R1 && hasMobileElements!R2) |
| auto moveBack() |
| { |
| return condition ? r1.moveBack : r2.moveBack; |
| } |
| |
| @property void back(T)(T v) |
| if (is(typeof({ r1.back = v; r2.back = v; }))) |
| { |
| if (condition) r1.back = v; else r2.back = v; |
| } |
| } |
| |
| static if (hasLength!R1 && hasLength!R2) |
| { |
| @property size_t length() |
| { |
| return condition ? r1.length : r2.length; |
| } |
| alias opDollar = length; |
| } |
| |
| static if (isRandomAccessRange!R1 && isRandomAccessRange!R2) |
| { |
| auto ref opIndex(size_t index) |
| { |
| return condition ? r1[index] : r2[index]; |
| } |
| |
| static if (hasMobileElements!R1 && hasMobileElements!R2) |
| auto moveAt(size_t index) |
| { |
| return condition ? r1.moveAt(index) : r2.moveAt(index); |
| } |
| |
| void opIndexAssign(T)(T v, size_t index) |
| if (is(typeof({ r1[1] = v; r2[1] = v; }))) |
| { |
| if (condition) r1[index] = v; else r2[index] = v; |
| } |
| } |
| |
| // BUG: this should work for infinite ranges, too |
| static if (hasSlicing!R1 && hasSlicing!R2 && |
| !isInfinite!R2 && !isInfinite!R2) |
| auto opSlice(size_t begin, size_t end) |
| { |
| auto result = this; |
| if (condition) result.r1 = result.r1[begin .. end]; |
| else result.r2 = result.r2[begin .. end]; |
| return result; |
| } |
| } |
| return Result(condition, r1, r2); |
| } |
| |
| /// |
| @system unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : filter, map; |
| |
| auto data1 = [ 1, 2, 3, 4 ].filter!(a => a != 3); |
| auto data2 = [ 5, 6, 7, 8 ].map!(a => a + 1); |
| |
| // choose() is primarily useful when you need to select one of two ranges |
| // with different types at runtime. |
| static assert(!is(typeof(data1) == typeof(data2))); |
| |
| auto chooseRange(bool pickFirst) |
| { |
| // The returned range is a common wrapper type that can be used for |
| // returning or storing either range without running into a type error. |
| return choose(pickFirst, data1, data2); |
| |
| // Simply returning the chosen range without using choose() does not |
| // work, because map() and filter() return different types. |
| //return pickFirst ? data1 : data2; // does not compile |
| } |
| |
| auto result = chooseRange(true); |
| assert(result.equal([ 1, 2, 4 ])); |
| |
| result = chooseRange(false); |
| assert(result.equal([ 6, 7, 8, 9 ])); |
| } |
| |
| /** |
| Choose one of multiple ranges at runtime. |
| |
| The ranges may be different, but they must have compatible element types. The |
| result is a range that offers the weakest capabilities of all $(D Ranges). |
| |
| Params: |
| index = which range to choose, must be less than the number of ranges |
| rs = two or more ranges |
| |
| Returns: |
| The indexed range. If rs consists of only one range, the return type is an |
| alias of that range's type. |
| */ |
| auto chooseAmong(Ranges...)(size_t index, Ranges rs) |
| if (Ranges.length >= 2 |
| && allSatisfy!(isInputRange, staticMap!(Unqual, Ranges)) |
| && !is(CommonType!(staticMap!(ElementType, Ranges)) == void)) |
| { |
| static if (Ranges.length == 2) |
| return choose(index == 0, rs[0], rs[1]); |
| else |
| return choose(index == 0, rs[0], chooseAmong(index - 1, rs[1 .. $])); |
| } |
| |
| /// |
| @system unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| int[] arr1 = [ 1, 2, 3, 4 ]; |
| int[] arr2 = [ 5, 6 ]; |
| int[] arr3 = [ 7 ]; |
| |
| { |
| auto s = chooseAmong(0, arr1, arr2, arr3); |
| auto t = s.save; |
| assert(s.length == 4); |
| assert(s[2] == 3); |
| s.popFront(); |
| assert(equal(t, [1, 2, 3, 4][])); |
| } |
| { |
| auto s = chooseAmong(1, arr1, arr2, arr3); |
| assert(s.length == 2); |
| s.front = 8; |
| assert(equal(s, [8, 6][])); |
| } |
| { |
| auto s = chooseAmong(1, arr1, arr2, arr3); |
| assert(s.length == 2); |
| s[1] = 9; |
| assert(equal(s, [8, 9][])); |
| } |
| { |
| auto s = chooseAmong(1, arr2, arr1, arr3)[1 .. 3]; |
| assert(s.length == 2); |
| assert(equal(s, [2, 3][])); |
| } |
| { |
| auto s = chooseAmong(0, arr1, arr2, arr3); |
| assert(s.length == 4); |
| assert(s.back == 4); |
| s.popBack(); |
| s.back = 5; |
| assert(equal(s, [1, 2, 5][])); |
| s.back = 3; |
| assert(equal(s, [1, 2, 3][])); |
| } |
| { |
| uint[] foo = [1,2,3,4,5]; |
| uint[] bar = [6,7,8,9,10]; |
| auto c = chooseAmong(1,foo, bar); |
| assert(c[3] == 9); |
| c[3] = 42; |
| assert(c[3] == 42); |
| assert(c.moveFront() == 6); |
| assert(c.moveBack() == 10); |
| assert(c.moveAt(4) == 10); |
| } |
| { |
| import std.range : cycle; |
| auto s = chooseAmong(1, cycle(arr2), cycle(arr3)); |
| assert(isInfinite!(typeof(s))); |
| assert(!s.empty); |
| assert(s[100] == 7); |
| } |
| } |
| |
| @system unittest |
| { |
| int[] a = [1, 2, 3]; |
| long[] b = [4, 5, 6]; |
| auto c = chooseAmong(0, a, b); |
| c[0] = 42; |
| assert(c[0] == 42); |
| } |
| |
| |
| /** |
| $(D roundRobin(r1, r2, r3)) yields $(D r1.front), then $(D r2.front), |
| then $(D r3.front), after which it pops off one element from each and |
| continues again from $(D r1). For example, if two ranges are involved, |
| it alternately yields elements off the two ranges. $(D roundRobin) |
| stops after it has consumed all ranges (skipping over the ones that |
| finish early). |
| */ |
| auto roundRobin(Rs...)(Rs rs) |
| if (Rs.length > 1 && allSatisfy!(isInputRange, staticMap!(Unqual, Rs))) |
| { |
| struct Result |
| { |
| import std.conv : to; |
| |
| public Rs source; |
| private size_t _current = size_t.max; |
| |
| @property bool empty() |
| { |
| foreach (i, Unused; Rs) |
| { |
| if (!source[i].empty) return false; |
| } |
| return true; |
| } |
| |
| @property auto ref front() |
| { |
| final switch (_current) |
| { |
| foreach (i, R; Rs) |
| { |
| case i: |
| assert( |
| !source[i].empty, |
| "Attempting to fetch the front of an empty roundRobin" |
| ); |
| return source[i].front; |
| } |
| } |
| assert(0); |
| } |
| |
| void popFront() |
| { |
| final switch (_current) |
| { |
| foreach (i, R; Rs) |
| { |
| case i: |
| source[i].popFront(); |
| break; |
| } |
| } |
| |
| auto next = _current == (Rs.length - 1) ? 0 : (_current + 1); |
| final switch (next) |
| { |
| foreach (i, R; Rs) |
| { |
| case i: |
| if (!source[i].empty) |
| { |
| _current = i; |
| return; |
| } |
| if (i == _current) |
| { |
| _current = _current.max; |
| return; |
| } |
| goto case (i + 1) % Rs.length; |
| } |
| } |
| } |
| |
| static if (allSatisfy!(isForwardRange, staticMap!(Unqual, Rs))) |
| @property auto save() |
| { |
| Result result = this; |
| foreach (i, Unused; Rs) |
| { |
| result.source[i] = result.source[i].save; |
| } |
| return result; |
| } |
| |
| static if (allSatisfy!(hasLength, Rs)) |
| { |
| @property size_t length() |
| { |
| size_t result; |
| foreach (i, R; Rs) |
| { |
| result += source[i].length; |
| } |
| return result; |
| } |
| |
| alias opDollar = length; |
| } |
| } |
| |
| return Result(rs, 0); |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| int[] a = [ 1, 2, 3 ]; |
| int[] b = [ 10, 20, 30, 40 ]; |
| auto r = roundRobin(a, b); |
| assert(equal(r, [ 1, 10, 2, 20, 3, 30, 40 ])); |
| } |
| |
| /** |
| * roundRobin can be used to create "interleave" functionality which inserts |
| * an element between each element in a range. |
| */ |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| auto interleave(R, E)(R range, E element) |
| if ((isInputRange!R && hasLength!R) || isForwardRange!R) |
| { |
| static if (hasLength!R) |
| immutable len = range.length; |
| else |
| immutable len = range.save.walkLength; |
| |
| return roundRobin( |
| range, |
| element.repeat(len - 1) |
| ); |
| } |
| |
| assert(interleave([1, 2, 3], 0).equal([1, 0, 2, 0, 3])); |
| } |
| |
| /** |
| Iterates a random-access range starting from a given point and |
| progressively extending left and right from that point. If no initial |
| point is given, iteration starts from the middle of the |
| range. Iteration spans the entire range. |
| |
| When `startingIndex` is 0 the range will be fully iterated in order |
| and in reverse order when `r.length` is given. |
| |
| Params: |
| r = a random access range with length and slicing |
| startingIndex = the index to begin iteration from |
| |
| Returns: |
| A forward range with length |
| */ |
| auto radial(Range, I)(Range r, I startingIndex) |
| if (isRandomAccessRange!(Unqual!Range) && hasLength!(Unqual!Range) && hasSlicing!(Unqual!Range) && isIntegral!I) |
| { |
| if (startingIndex != r.length) ++startingIndex; |
| return roundRobin(retro(r[0 .. startingIndex]), r[startingIndex .. r.length]); |
| } |
| |
| /// Ditto |
| auto radial(R)(R r) |
| if (isRandomAccessRange!(Unqual!R) && hasLength!(Unqual!R) && hasSlicing!(Unqual!R)) |
| { |
| return .radial(r, (r.length - !r.empty) / 2); |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| int[] a = [ 1, 2, 3, 4, 5 ]; |
| assert(equal(radial(a), [ 3, 4, 2, 5, 1 ])); |
| a = [ 1, 2, 3, 4 ]; |
| assert(equal(radial(a), [ 2, 3, 1, 4 ])); |
| |
| // If the left end is reached first, the remaining elements on the right |
| // are concatenated in order: |
| a = [ 0, 1, 2, 3, 4, 5 ]; |
| assert(equal(radial(a, 1), [ 1, 2, 0, 3, 4, 5 ])); |
| |
| // If the right end is reached first, the remaining elements on the left |
| // are concatenated in reverse order: |
| assert(equal(radial(a, 4), [ 4, 5, 3, 2, 1, 0 ])); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.conv : text; |
| import std.exception : enforce; |
| import std.internal.test.dummyrange : DummyRange, Length, RangeType, ReturnBy; |
| |
| void test(int[] input, int[] witness) |
| { |
| enforce(equal(radial(input), witness), |
| text(radial(input), " vs. ", witness)); |
| } |
| test([], []); |
| test([ 1 ], [ 1 ]); |
| test([ 1, 2 ], [ 1, 2 ]); |
| test([ 1, 2, 3 ], [ 2, 3, 1 ]); |
| test([ 1, 2, 3, 4 ], [ 2, 3, 1, 4 ]); |
| test([ 1, 2, 3, 4, 5 ], [ 3, 4, 2, 5, 1 ]); |
| test([ 1, 2, 3, 4, 5, 6 ], [ 3, 4, 2, 5, 1, 6 ]); |
| |
| int[] a = [ 1, 2, 3, 4, 5 ]; |
| assert(equal(radial(a, 1), [ 2, 3, 1, 4, 5 ])); |
| assert(equal(radial(a, 0), [ 1, 2, 3, 4, 5 ])); // only right subrange |
| assert(equal(radial(a, a.length), [ 5, 4, 3, 2, 1 ])); // only left subrange |
| static assert(isForwardRange!(typeof(radial(a, 1)))); |
| |
| auto r = radial([1,2,3,4,5]); |
| for (auto rr = r.save; !rr.empty; rr.popFront()) |
| { |
| assert(rr.front == moveFront(rr)); |
| } |
| r.front = 5; |
| assert(r.front == 5); |
| |
| // Test instantiation without lvalue elements. |
| DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random) dummy; |
| assert(equal(radial(dummy, 4), [5, 6, 4, 7, 3, 8, 2, 9, 1, 10])); |
| |
| // immutable int[] immi = [ 1, 2 ]; |
| // static assert(is(typeof(radial(immi)))); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| auto LL = iota(1L, 6L); |
| auto r = radial(LL); |
| assert(equal(r, [3L, 4L, 2L, 5L, 1L])); |
| } |
| |
| /** |
| Lazily takes only up to `n` elements of a range. This is |
| particularly useful when using with infinite ranges. |
| |
| Unlike $(LREF takeExactly), `take` does not require that there |
| are `n` or more elements in `input`. As a consequence, length |
| information is not applied to the result unless `input` also has |
| length information. |
| |
| Params: |
| input = an input range to iterate over up to `n` times |
| n = the number of elements to take |
| |
| Returns: |
| At minimum, an input range. If the range offers random access |
| and `length`, `take` offers them as well. |
| */ |
| Take!R take(R)(R input, size_t n) |
| if (isInputRange!(Unqual!R)) |
| { |
| alias U = Unqual!R; |
| static if (is(R T == Take!T)) |
| { |
| import std.algorithm.comparison : min; |
| return R(input.source, min(n, input._maxAvailable)); |
| } |
| else static if (!isInfinite!U && hasSlicing!U) |
| { |
| import std.algorithm.comparison : min; |
| return input[0 .. min(n, input.length)]; |
| } |
| else |
| { |
| return Take!R(input, n); |
| } |
| } |
| |
| /// ditto |
| struct Take(Range) |
| if (isInputRange!(Unqual!Range) && |
| //take _cannot_ test hasSlicing on infinite ranges, because hasSlicing uses |
| //take for slicing infinite ranges. |
| !((!isInfinite!(Unqual!Range) && hasSlicing!(Unqual!Range)) || is(Range T == Take!T))) |
| { |
| private alias R = Unqual!Range; |
| |
| /// User accessible in read and write |
| public R source; |
| |
| private size_t _maxAvailable; |
| |
| alias Source = R; |
| |
| /// Range primitives |
| @property bool empty() |
| { |
| return _maxAvailable == 0 || source.empty; |
| } |
| |
| /// ditto |
| @property auto ref front() |
| { |
| assert(!empty, |
| "Attempting to fetch the front of an empty " |
| ~ Take.stringof); |
| return source.front; |
| } |
| |
| /// ditto |
| void popFront() |
| { |
| assert(!empty, |
| "Attempting to popFront() past the end of a " |
| ~ Take.stringof); |
| source.popFront(); |
| --_maxAvailable; |
| } |
| |
| static if (isForwardRange!R) |
| /// ditto |
| @property Take save() |
| { |
| return Take(source.save, _maxAvailable); |
| } |
| |
| static if (hasAssignableElements!R) |
| /// ditto |
| @property void front(ElementType!R v) |
| { |
| assert(!empty, |
| "Attempting to assign to the front of an empty " |
| ~ Take.stringof); |
| // This has to return auto instead of void because of Bug 4706. |
| source.front = v; |
| } |
| |
| static if (hasMobileElements!R) |
| { |
| /// ditto |
| auto moveFront() |
| { |
| assert(!empty, |
| "Attempting to move the front of an empty " |
| ~ Take.stringof); |
| return source.moveFront(); |
| } |
| } |
| |
| static if (isInfinite!R) |
| { |
| /// ditto |
| @property size_t length() const |
| { |
| return _maxAvailable; |
| } |
| |
| /// ditto |
| alias opDollar = length; |
| |
| //Note: Due to Take/hasSlicing circular dependency, |
| //This needs to be a restrained template. |
| /// ditto |
| auto opSlice()(size_t i, size_t j) |
| if (hasSlicing!R) |
| { |
| assert(i <= j, "Invalid slice bounds"); |
| assert(j <= length, "Attempting to slice past the end of a " |
| ~ Take.stringof); |
| return source[i .. j]; |
| } |
| } |
| else static if (hasLength!R) |
| { |
| /// ditto |
| @property size_t length() |
| { |
| import std.algorithm.comparison : min; |
| return min(_maxAvailable, source.length); |
| } |
| |
| alias opDollar = length; |
| } |
| |
| static if (isRandomAccessRange!R) |
| { |
| /// ditto |
| void popBack() |
| { |
| assert(!empty, |
| "Attempting to popBack() past the beginning of a " |
| ~ Take.stringof); |
| --_maxAvailable; |
| } |
| |
| /// ditto |
| @property auto ref back() |
| { |
| assert(!empty, |
| "Attempting to fetch the back of an empty " |
| ~ Take.stringof); |
| return source[this.length - 1]; |
| } |
| |
| /// ditto |
| auto ref opIndex(size_t index) |
| { |
| assert(index < length, |
| "Attempting to index out of the bounds of a " |
| ~ Take.stringof); |
| return source[index]; |
| } |
| |
| static if (hasAssignableElements!R) |
| { |
| /// ditto |
| @property void back(ElementType!R v) |
| { |
| // This has to return auto instead of void because of Bug 4706. |
| assert(!empty, |
| "Attempting to assign to the back of an empty " |
| ~ Take.stringof); |
| source[this.length - 1] = v; |
| } |
| |
| /// ditto |
| void opIndexAssign(ElementType!R v, size_t index) |
| { |
| assert(index < length, |
| "Attempting to index out of the bounds of a " |
| ~ Take.stringof); |
| source[index] = v; |
| } |
| } |
| |
| static if (hasMobileElements!R) |
| { |
| /// ditto |
| auto moveBack() |
| { |
| assert(!empty, |
| "Attempting to move the back of an empty " |
| ~ Take.stringof); |
| return source.moveAt(this.length - 1); |
| } |
| |
| /// ditto |
| auto moveAt(size_t index) |
| { |
| assert(index < length, |
| "Attempting to index out of the bounds of a " |
| ~ Take.stringof); |
| return source.moveAt(index); |
| } |
| } |
| } |
| |
| /** |
| Access to maximal length of the range. |
| Note: the actual length of the range depends on the underlying range. |
| If it has fewer elements, it will stop before maxLength is reached. |
| */ |
| @property size_t maxLength() const |
| { |
| return _maxAvailable; |
| } |
| } |
| |
| /** |
| This template simply aliases itself to R and is useful for consistency in |
| generic code. |
| */ |
| template Take(R) |
| if (isInputRange!(Unqual!R) && |
| ((!isInfinite!(Unqual!R) && hasSlicing!(Unqual!R)) || is(R T == Take!T))) |
| { |
| alias Take = R; |
| } |
| |
| /// |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| int[] arr1 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; |
| auto s = take(arr1, 5); |
| assert(s.length == 5); |
| assert(s[4] == 5); |
| assert(equal(s, [ 1, 2, 3, 4, 5 ][])); |
| } |
| |
| /** |
| * If the range runs out before `n` elements, `take` simply returns the entire |
| * range (unlike $(LREF takeExactly), which will cause an assertion failure if |
| * the range ends prematurely): |
| */ |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| int[] arr2 = [ 1, 2, 3 ]; |
| auto t = take(arr2, 5); |
| assert(t.length == 3); |
| assert(equal(t, [ 1, 2, 3 ])); |
| } |
| |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.internal.test.dummyrange : AllDummyRanges; |
| |
| int[] arr1 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; |
| auto s = take(arr1, 5); |
| assert(s.length == 5); |
| assert(s[4] == 5); |
| assert(equal(s, [ 1, 2, 3, 4, 5 ][])); |
| assert(equal(retro(s), [ 5, 4, 3, 2, 1 ][])); |
| |
| // Test fix for bug 4464. |
| static assert(is(typeof(s) == Take!(int[]))); |
| static assert(is(typeof(s) == int[])); |
| |
| // Test using narrow strings. |
| import std.exception : assumeWontThrow; |
| |
| auto myStr = "This is a string."; |
| auto takeMyStr = take(myStr, 7); |
| assert(assumeWontThrow(equal(takeMyStr, "This is"))); |
| // Test fix for bug 5052. |
| auto takeMyStrAgain = take(takeMyStr, 4); |
| assert(assumeWontThrow(equal(takeMyStrAgain, "This"))); |
| static assert(is (typeof(takeMyStrAgain) == typeof(takeMyStr))); |
| takeMyStrAgain = take(takeMyStr, 10); |
| assert(assumeWontThrow(equal(takeMyStrAgain, "This is"))); |
| |
| foreach (DummyType; AllDummyRanges) |
| { |
| DummyType dummy; |
| auto t = take(dummy, 5); |
| alias T = typeof(t); |
| |
| static if (isRandomAccessRange!DummyType) |
| { |
| static assert(isRandomAccessRange!T); |
| assert(t[4] == 5); |
| |
| assert(moveAt(t, 1) == t[1]); |
| assert(t.back == moveBack(t)); |
| } |
| else static if (isForwardRange!DummyType) |
| { |
| static assert(isForwardRange!T); |
| } |
| |
| for (auto tt = t; !tt.empty; tt.popFront()) |
| { |
| assert(tt.front == moveFront(tt)); |
| } |
| |
| // Bidirectional ranges can't be propagated properly if they don't |
| // also have random access. |
| |
| assert(equal(t, [1,2,3,4,5])); |
| |
| //Test that take doesn't wrap the result of take. |
| assert(take(t, 4) == take(dummy, 4)); |
| } |
| |
| immutable myRepeat = repeat(1); |
| static assert(is(Take!(typeof(myRepeat)))); |
| } |
| |
| pure @safe nothrow @nogc unittest |
| { |
| //check for correct slicing of Take on an infinite range |
| import std.algorithm.comparison : equal; |
| foreach (start; 0 .. 4) |
| foreach (stop; start .. 4) |
| assert(iota(4).cycle.take(4)[start .. stop] |
| .equal(iota(start, stop))); |
| } |
| |
| pure @safe nothrow @nogc unittest |
| { |
| // Check that one can declare variables of all Take types, |
| // and that they match the return type of the corresponding |
| // take(). (See issue 4464.) |
| int[] r1; |
| Take!(int[]) t1; |
| t1 = take(r1, 1); |
| assert(t1.empty); |
| |
| string r2; |
| Take!string t2; |
| t2 = take(r2, 1); |
| assert(t2.empty); |
| |
| Take!(Take!string) t3; |
| t3 = take(t2, 1); |
| assert(t3.empty); |
| } |
| |
| pure @safe nothrow @nogc unittest |
| { |
| alias R1 = typeof(repeat(1)); |
| alias R2 = typeof(cycle([1])); |
| alias TR1 = Take!R1; |
| alias TR2 = Take!R2; |
| static assert(isBidirectionalRange!TR1); |
| static assert(isBidirectionalRange!TR2); |
| } |
| |
| pure @safe nothrow @nogc unittest //12731 |
| { |
| auto a = repeat(1); |
| auto s = a[1 .. 5]; |
| s = s[1 .. 3]; |
| assert(s.length == 2); |
| assert(s[0] == 1); |
| assert(s[1] == 1); |
| } |
| |
| pure @safe nothrow @nogc unittest //13151 |
| { |
| import std.algorithm.comparison : equal; |
| |
| auto r = take(repeat(1, 4), 3); |
| assert(r.take(2).equal(repeat(1, 2))); |
| } |
| |
| |
| /** |
| Similar to $(LREF take), but assumes that $(D range) has at least $(D |
| n) elements. Consequently, the result of $(D takeExactly(range, n)) |
| always defines the $(D length) property (and initializes it to $(D n)) |
| even when $(D range) itself does not define $(D length). |
| |
| The result of $(D takeExactly) is identical to that of $(LREF take) in |
| cases where the original range defines $(D length) or is infinite. |
| |
| Unlike $(LREF take), however, it is illegal to pass a range with less than |
| $(D n) elements to $(D takeExactly); this will cause an assertion failure. |
| */ |
| auto takeExactly(R)(R range, size_t n) |
| if (isInputRange!R) |
| { |
| static if (is(typeof(takeExactly(range._input, n)) == R)) |
| { |
| assert(n <= range._n, |
| "Attempted to take more than the length of the range with takeExactly."); |
| // takeExactly(takeExactly(r, n1), n2) has the same type as |
| // takeExactly(r, n1) and simply returns takeExactly(r, n2) |
| range._n = n; |
| return range; |
| } |
| //Also covers hasSlicing!R for finite ranges. |
| else static if (hasLength!R) |
| { |
| assert(n <= range.length, |
| "Attempted to take more than the length of the range with takeExactly."); |
| return take(range, n); |
| } |
| else static if (isInfinite!R) |
| return Take!R(range, n); |
| else |
| { |
| static struct Result |
| { |
| R _input; |
| private size_t _n; |
| |
| @property bool empty() const { return !_n; } |
| @property auto ref front() |
| { |
| assert(_n > 0, "front() on an empty " ~ Result.stringof); |
| return _input.front; |
| } |
| void popFront() { _input.popFront(); --_n; } |
| @property size_t length() const { return _n; } |
| alias opDollar = length; |
| |
| @property Take!R _takeExactly_Result_asTake() |
| { |
| return typeof(return)(_input, _n); |
| } |
| |
| alias _takeExactly_Result_asTake this; |
| |
| static if (isForwardRange!R) |
| @property auto save() |
| { |
| return Result(_input.save, _n); |
| } |
| |
| static if (hasMobileElements!R) |
| { |
| auto moveFront() |
| { |
| assert(!empty, |
| "Attempting to move the front of an empty " |
| ~ typeof(this).stringof); |
| return _input.moveFront(); |
| } |
| } |
| |
| static if (hasAssignableElements!R) |
| { |
| @property auto ref front(ElementType!R v) |
| { |
| assert(!empty, |
| "Attempting to assign to the front of an empty " |
| ~ typeof(this).stringof); |
| return _input.front = v; |
| } |
| } |
| } |
| |
| return Result(range, n); |
| } |
| } |
| |
| /// |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| auto a = [ 1, 2, 3, 4, 5 ]; |
| |
| auto b = takeExactly(a, 3); |
| assert(equal(b, [1, 2, 3])); |
| static assert(is(typeof(b.length) == size_t)); |
| assert(b.length == 3); |
| assert(b.front == 1); |
| assert(b.back == 3); |
| } |
| |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : filter; |
| |
| auto a = [ 1, 2, 3, 4, 5 ]; |
| auto b = takeExactly(a, 3); |
| assert(equal(b, [1, 2, 3])); |
| auto c = takeExactly(b, 2); |
| assert(equal(c, [1, 2])); |
| |
| |
| |
| auto d = filter!"a > 2"(a); |
| auto e = takeExactly(d, 3); |
| assert(equal(e, [3, 4, 5])); |
| static assert(is(typeof(e.length) == size_t)); |
| assert(e.length == 3); |
| assert(e.front == 3); |
| |
| assert(equal(takeExactly(e, 3), [3, 4, 5])); |
| } |
| |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.internal.test.dummyrange : AllDummyRanges; |
| |
| auto a = [ 1, 2, 3, 4, 5 ]; |
| //Test that take and takeExactly are the same for ranges which define length |
| //but aren't sliceable. |
| struct L |
| { |
| @property auto front() { return _arr[0]; } |
| @property bool empty() { return _arr.empty; } |
| void popFront() { _arr.popFront(); } |
| @property size_t length() { return _arr.length; } |
| int[] _arr; |
| } |
| static assert(is(typeof(take(L(a), 3)) == typeof(takeExactly(L(a), 3)))); |
| assert(take(L(a), 3) == takeExactly(L(a), 3)); |
| |
| //Test that take and takeExactly are the same for ranges which are sliceable. |
| static assert(is(typeof(take(a, 3)) == typeof(takeExactly(a, 3)))); |
| assert(take(a, 3) == takeExactly(a, 3)); |
| |
| //Test that take and takeExactly are the same for infinite ranges. |
| auto inf = repeat(1); |
| static assert(is(typeof(take(inf, 5)) == Take!(typeof(inf)))); |
| assert(take(inf, 5) == takeExactly(inf, 5)); |
| |
| //Test that take and takeExactly are _not_ the same for ranges which don't |
| //define length. |
| static assert(!is(typeof(take(filter!"true"(a), 3)) == typeof(takeExactly(filter!"true"(a), 3)))); |
| |
| foreach (DummyType; AllDummyRanges) |
| { |
| { |
| DummyType dummy; |
| auto t = takeExactly(dummy, 5); |
| |
| //Test that takeExactly doesn't wrap the result of takeExactly. |
| assert(takeExactly(t, 4) == takeExactly(dummy, 4)); |
| } |
| |
| static if (hasMobileElements!DummyType) |
| { |
| { |
| auto t = takeExactly(DummyType.init, 4); |
| assert(t.moveFront() == 1); |
| assert(equal(t, [1, 2, 3, 4])); |
| } |
| } |
| |
| static if (hasAssignableElements!DummyType) |
| { |
| { |
| auto t = takeExactly(DummyType.init, 4); |
| t.front = 9; |
| assert(equal(t, [9, 2, 3, 4])); |
| } |
| } |
| } |
| } |
| |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.internal.test.dummyrange : DummyRange, Length, RangeType, ReturnBy; |
| |
| alias DummyType = DummyRange!(ReturnBy.Value, Length.No, RangeType.Forward); |
| auto te = takeExactly(DummyType(), 5); |
| Take!DummyType t = te; |
| assert(equal(t, [1, 2, 3, 4, 5])); |
| assert(equal(t, te)); |
| } |
| |
| /** |
| Returns a range with at most one element; for example, $(D |
| takeOne([42, 43, 44])) returns a range consisting of the integer $(D |
| 42). Calling $(D popFront()) off that range renders it empty. |
| |
| In effect $(D takeOne(r)) is somewhat equivalent to $(D take(r, 1)) but in |
| certain interfaces it is important to know statically that the range may only |
| have at most one element. |
| |
| The type returned by $(D takeOne) is a random-access range with length |
| regardless of $(D R)'s capabilities, as long as it is a forward range. |
| (another feature that distinguishes $(D takeOne) from $(D take)). If |
| (D R) is an input range but not a forward range, return type is an input |
| range with all random-access capabilities except save. |
| */ |
| auto takeOne(R)(R source) |
| if (isInputRange!R) |
| { |
| static if (hasSlicing!R) |
| { |
| return source[0 .. !source.empty]; |
| } |
| else |
| { |
| static struct Result |
| { |
| private R _source; |
| private bool _empty = true; |
| @property bool empty() const { return _empty; } |
| @property auto ref front() |
| { |
| assert(!empty, "Attempting to fetch the front of an empty takeOne"); |
| return _source.front; |
| } |
| void popFront() |
| { |
| assert(!empty, "Attempting to popFront an empty takeOne"); |
| _source.popFront(); |
| _empty = true; |
| } |
| void popBack() |
| { |
| assert(!empty, "Attempting to popBack an empty takeOne"); |
| _source.popFront(); |
| _empty = true; |
| } |
| static if (isForwardRange!(Unqual!R)) |
| { |
| @property auto save() { return Result(_source.save, empty); } |
| } |
| @property auto ref back() |
| { |
| assert(!empty, "Attempting to fetch the back of an empty takeOne"); |
| return _source.front; |
| } |
| @property size_t length() const { return !empty; } |
| alias opDollar = length; |
| auto ref opIndex(size_t n) |
| { |
| assert(n < length, "Attempting to index a takeOne out of bounds"); |
| return _source.front; |
| } |
| auto opSlice(size_t m, size_t n) |
| { |
| assert(m <= n && n < length, "Attempting to index a takeOne out of bounds"); |
| return n > m ? this : Result(_source, false); |
| } |
| // Non-standard property |
| @property R source() { return _source; } |
| } |
| |
| return Result(source, source.empty); |
| } |
| } |
| |
| /// |
| pure @safe nothrow unittest |
| { |
| auto s = takeOne([42, 43, 44]); |
| static assert(isRandomAccessRange!(typeof(s))); |
| assert(s.length == 1); |
| assert(!s.empty); |
| assert(s.front == 42); |
| s.front = 43; |
| assert(s.front == 43); |
| assert(s.back == 43); |
| assert(s[0] == 43); |
| s.popFront(); |
| assert(s.length == 0); |
| assert(s.empty); |
| } |
| |
| pure @safe nothrow @nogc unittest |
| { |
| struct NonForwardRange |
| { |
| enum empty = false; |
| int front() { return 42; } |
| void popFront() {} |
| } |
| |
| static assert(!isForwardRange!NonForwardRange); |
| |
| auto s = takeOne(NonForwardRange()); |
| assert(s.front == 42); |
| } |
| |
| //guards against issue 16999 |
| pure @safe unittest |
| { |
| auto myIota = new class |
| { |
| int front = 0; |
| @safe void popFront(){front++;} |
| enum empty = false; |
| }; |
| auto iotaPart = myIota.takeOne; |
| int sum; |
| foreach (var; chain(iotaPart, iotaPart, iotaPart)) |
| { |
| sum += var; |
| } |
| assert(sum == 3); |
| assert(iotaPart.front == 3); |
| } |
| |
| /++ |
| Returns an empty range which is statically known to be empty and is |
| guaranteed to have $(D length) and be random access regardless of $(D R)'s |
| capabilities. |
| +/ |
| auto takeNone(R)() |
| if (isInputRange!R) |
| { |
| return typeof(takeOne(R.init)).init; |
| } |
| |
| /// |
| pure @safe nothrow @nogc unittest |
| { |
| auto range = takeNone!(int[])(); |
| assert(range.length == 0); |
| assert(range.empty); |
| } |
| |
| pure @safe nothrow @nogc unittest |
| { |
| enum ctfe = takeNone!(int[])(); |
| static assert(ctfe.length == 0); |
| static assert(ctfe.empty); |
| } |
| |
| |
| /++ |
| Creates an empty range from the given range in $(BIGOH 1). If it can, it |
| will return the same range type. If not, it will return |
| $(D takeExactly(range, 0)). |
| +/ |
| auto takeNone(R)(R range) |
| if (isInputRange!R) |
| { |
| import std.traits : isDynamicArray; |
| //Makes it so that calls to takeNone which don't use UFCS still work with a |
| //member version if it's defined. |
| static if (is(typeof(R.takeNone))) |
| auto retval = range.takeNone(); |
| //@@@BUG@@@ 8339 |
| else static if (isDynamicArray!R)/+ || |
| (is(R == struct) && __traits(compiles, {auto r = R.init;}) && R.init.empty))+/ |
| { |
| auto retval = R.init; |
| } |
| //An infinite range sliced at [0 .. 0] would likely still not be empty... |
| else static if (hasSlicing!R && !isInfinite!R) |
| auto retval = range[0 .. 0]; |
| else |
| auto retval = takeExactly(range, 0); |
| |
| //@@@BUG@@@ 7892 prevents this from being done in an out block. |
| assert(retval.empty); |
| return retval; |
| } |
| |
| /// |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.iteration : filter; |
| assert(takeNone([42, 27, 19]).empty); |
| assert(takeNone("dlang.org").empty); |
| assert(takeNone(filter!"true"([42, 27, 19])).empty); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.iteration : filter; |
| import std.meta : AliasSeq; |
| |
| struct Dummy |
| { |
| mixin template genInput() |
| { |
| @safe: |
| @property bool empty() { return _arr.empty; } |
| @property auto front() { return _arr.front; } |
| void popFront() { _arr.popFront(); } |
| static assert(isInputRange!(typeof(this))); |
| } |
| } |
| alias genInput = Dummy.genInput; |
| |
| static struct NormalStruct |
| { |
| //Disabled to make sure that the takeExactly version is used. |
| @disable this(); |
| this(int[] arr) { _arr = arr; } |
| mixin genInput; |
| int[] _arr; |
| } |
| |
| static struct SliceStruct |
| { |
| @disable this(); |
| this(int[] arr) { _arr = arr; } |
| mixin genInput; |
| @property auto save() { return this; } |
| auto opSlice(size_t i, size_t j) { return typeof(this)(_arr[i .. j]); } |
| @property size_t length() { return _arr.length; } |
| int[] _arr; |
| } |
| |
| static struct InitStruct |
| { |
| mixin genInput; |
| int[] _arr; |
| } |
| |
| static struct TakeNoneStruct |
| { |
| this(int[] arr) { _arr = arr; } |
| @disable this(); |
| mixin genInput; |
| auto takeNone() { return typeof(this)(null); } |
| int[] _arr; |
| } |
| |
| static class NormalClass |
| { |
| this(int[] arr) {_arr = arr;} |
| mixin genInput; |
| int[] _arr; |
| } |
| |
| static class SliceClass |
| { |
| @safe: |
| this(int[] arr) { _arr = arr; } |
| mixin genInput; |
| @property auto save() { return new typeof(this)(_arr); } |
| auto opSlice(size_t i, size_t j) { return new typeof(this)(_arr[i .. j]); } |
| @property size_t length() { return _arr.length; } |
| int[] _arr; |
| } |
| |
| static class TakeNoneClass |
| { |
| @safe: |
| this(int[] arr) { _arr = arr; } |
| mixin genInput; |
| auto takeNone() { return new typeof(this)(null); } |
| int[] _arr; |
| } |
| |
| import std.format : format; |
| |
| foreach (range; AliasSeq!([1, 2, 3, 4, 5], |
| "hello world", |
| "hello world"w, |
| "hello world"d, |
| SliceStruct([1, 2, 3]), |
| //@@@BUG@@@ 8339 forces this to be takeExactly |
| //`InitStruct([1, 2, 3]), |
| TakeNoneStruct([1, 2, 3]))) |
| { |
| static assert(takeNone(range).empty, typeof(range).stringof); |
| assert(takeNone(range).empty); |
| static assert(is(typeof(range) == typeof(takeNone(range))), typeof(range).stringof); |
| } |
| |
| foreach (range; AliasSeq!(NormalStruct([1, 2, 3]), |
| InitStruct([1, 2, 3]))) |
| { |
| static assert(takeNone(range).empty, typeof(range).stringof); |
| assert(takeNone(range).empty); |
| static assert(is(typeof(takeExactly(range, 0)) == typeof(takeNone(range))), typeof(range).stringof); |
| } |
| |
| //Don't work in CTFE. |
| auto normal = new NormalClass([1, 2, 3]); |
| assert(takeNone(normal).empty); |
| static assert(is(typeof(takeExactly(normal, 0)) == typeof(takeNone(normal))), typeof(normal).stringof); |
| |
| auto slice = new SliceClass([1, 2, 3]); |
| assert(takeNone(slice).empty); |
| static assert(is(SliceClass == typeof(takeNone(slice))), typeof(slice).stringof); |
| |
| auto taken = new TakeNoneClass([1, 2, 3]); |
| assert(takeNone(taken).empty); |
| static assert(is(TakeNoneClass == typeof(takeNone(taken))), typeof(taken).stringof); |
| |
| auto filtered = filter!"true"([1, 2, 3, 4, 5]); |
| assert(takeNone(filtered).empty); |
| //@@@BUG@@@ 8339 and 5941 force this to be takeExactly |
| //static assert(is(typeof(filtered) == typeof(takeNone(filtered))), typeof(filtered).stringof); |
| } |
| |
| /++ |
| + Return a _range advanced to within $(D _n) elements of the end of |
| + $(D _range). |
| + |
| + Intended as the _range equivalent of the Unix |
| + $(HTTP en.wikipedia.org/wiki/Tail_%28Unix%29, _tail) utility. When the length |
| + of $(D _range) is less than or equal to $(D _n), $(D _range) is returned |
| + as-is. |
| + |
| + Completes in $(BIGOH 1) steps for ranges that support slicing and have |
| + length. Completes in $(BIGOH _range.length) time for all other ranges. |
| + |
| + Params: |
| + range = _range to get _tail of |
| + n = maximum number of elements to include in _tail |
| + |
| + Returns: |
| + Returns the _tail of $(D _range) augmented with length information |
| +/ |
| auto tail(Range)(Range range, size_t n) |
| if (isInputRange!Range && !isInfinite!Range && |
| (hasLength!Range || isForwardRange!Range)) |
| { |
| static if (hasLength!Range) |
| { |
| immutable length = range.length; |
| if (n >= length) |
| return range.takeExactly(length); |
| else |
| return range.drop(length - n).takeExactly(n); |
| } |
| else |
| { |
| Range scout = range.save; |
| foreach (immutable i; 0 .. n) |
| { |
| if (scout.empty) |
| return range.takeExactly(i); |
| scout.popFront(); |
| } |
| |
| auto tail = range.save; |
| while (!scout.empty) |
| { |
| assert(!tail.empty); |
| scout.popFront(); |
| tail.popFront(); |
| } |
| |
| return tail.takeExactly(n); |
| } |
| } |
| |
| /// |
| pure @safe nothrow unittest |
| { |
| // tail -c n |
| assert([1, 2, 3].tail(1) == [3]); |
| assert([1, 2, 3].tail(2) == [2, 3]); |
| assert([1, 2, 3].tail(3) == [1, 2, 3]); |
| assert([1, 2, 3].tail(4) == [1, 2, 3]); |
| assert([1, 2, 3].tail(0).length == 0); |
| |
| // tail --lines=n |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : joiner; |
| import std.exception : assumeWontThrow; |
| import std.string : lineSplitter; |
| assert("one\ntwo\nthree" |
| .lineSplitter |
| .tail(2) |
| .joiner("\n") |
| .equal("two\nthree") |
| .assumeWontThrow); |
| } |
| |
| // @nogc prevented by @@@BUG@@@ 15408 |
| pure nothrow @safe /+@nogc+/ unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.internal.test.dummyrange : AllDummyRanges, DummyRange, Length, |
| RangeType, ReturnBy; |
| |
| static immutable cheatsheet = [6, 7, 8, 9, 10]; |
| |
| foreach (R; AllDummyRanges) |
| { |
| static if (isInputRange!R && !isInfinite!R && |
| (hasLength!R || isForwardRange!R)) |
| { |
| assert(R.init.tail(5).equal(cheatsheet)); |
| static assert(R.init.tail(5).equal(cheatsheet)); |
| |
| assert(R.init.tail(0).length == 0); |
| assert(R.init.tail(10).equal(R.init)); |
| assert(R.init.tail(11).equal(R.init)); |
| } |
| } |
| |
| // Infinite ranges are not supported |
| static assert(!__traits(compiles, repeat(0).tail(0))); |
| |
| // Neither are non-forward ranges without length |
| static assert(!__traits(compiles, DummyRange!(ReturnBy.Value, Length.No, |
| RangeType.Input).init.tail(5))); |
| } |
| |
| pure @safe nothrow @nogc unittest |
| { |
| static immutable input = [1, 2, 3]; |
| static immutable expectedOutput = [2, 3]; |
| assert(input.tail(2) == expectedOutput); |
| } |
| |
| /++ |
| Convenience function which calls |
| $(REF popFrontN, std, _range, primitives)`(range, n)` and returns `range`. |
| `drop` makes it easier to pop elements from a range |
| and then pass it to another function within a single expression, |
| whereas `popFrontN` would require multiple statements. |
| |
| `dropBack` provides the same functionality but instead calls |
| $(REF popBackN, std, _range, primitives)`(range, n)` |
| |
| Note: `drop` and `dropBack` will only pop $(I up to) |
| `n` elements but will stop if the range is empty first. |
| In other languages this is sometimes called `skip`. |
| |
| Params: |
| range = the input range to drop from |
| n = the number of elements to drop |
| |
| Returns: |
| `range` with up to `n` elements dropped |
| |
| See_Also: |
| $(REF popFront, std, _range, primitives), $(REF popBackN, std, _range, primitives) |
| +/ |
| R drop(R)(R range, size_t n) |
| if (isInputRange!R) |
| { |
| range.popFrontN(n); |
| return range; |
| } |
| /// ditto |
| R dropBack(R)(R range, size_t n) |
| if (isBidirectionalRange!R) |
| { |
| range.popBackN(n); |
| return range; |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| assert([0, 2, 1, 5, 0, 3].drop(3) == [5, 0, 3]); |
| assert("hello world".drop(6) == "world"); |
| assert("hello world".drop(50).empty); |
| assert("hello world".take(6).drop(3).equal("lo ")); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| assert([0, 2, 1, 5, 0, 3].dropBack(3) == [0, 2, 1]); |
| assert("hello world".dropBack(6) == "hello"); |
| assert("hello world".dropBack(50).empty); |
| assert("hello world".drop(4).dropBack(4).equal("o w")); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.container.dlist : DList; |
| |
| //Remove all but the first two elements |
| auto a = DList!int(0, 1, 9, 9, 9, 9); |
| a.remove(a[].drop(2)); |
| assert(a[].equal(a[].take(2))); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : filter; |
| |
| assert(drop("", 5).empty); |
| assert(equal(drop(filter!"true"([0, 2, 1, 5, 0, 3]), 3), [5, 0, 3])); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.container.dlist : DList; |
| |
| //insert before the last two elements |
| auto a = DList!int(0, 1, 2, 5, 6); |
| a.insertAfter(a[].dropBack(2), [3, 4]); |
| assert(a[].equal(iota(0, 7))); |
| } |
| |
| /++ |
| Similar to $(LREF drop) and $(D dropBack) but they call |
| $(D range.$(LREF popFrontExactly)(n)) and $(D range.popBackExactly(n)) |
| instead. |
| |
| Note: Unlike $(D drop), $(D dropExactly) will assume that the |
| range holds at least $(D n) elements. This makes $(D dropExactly) |
| faster than $(D drop), but it also means that if $(D range) does |
| not contain at least $(D n) elements, it will attempt to call $(D popFront) |
| on an empty range, which is undefined behavior. So, only use |
| $(D popFrontExactly) when it is guaranteed that $(D range) holds at least |
| $(D n) elements. |
| |
| Params: |
| range = the input range to drop from |
| n = the number of elements to drop |
| |
| Returns: |
| `range` with `n` elements dropped |
| |
| See_Also: |
| $(REF popFrontExcatly, std, _range, primitives), |
| $(REF popBackExcatly, std, _range, primitives) |
| +/ |
| R dropExactly(R)(R range, size_t n) |
| if (isInputRange!R) |
| { |
| popFrontExactly(range, n); |
| return range; |
| } |
| /// ditto |
| R dropBackExactly(R)(R range, size_t n) |
| if (isBidirectionalRange!R) |
| { |
| popBackExactly(range, n); |
| return range; |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : filterBidirectional; |
| |
| auto a = [1, 2, 3]; |
| assert(a.dropExactly(2) == [3]); |
| assert(a.dropBackExactly(2) == [1]); |
| |
| string s = "日本語"; |
| assert(s.dropExactly(2) == "語"); |
| assert(s.dropBackExactly(2) == "æ—¥"); |
| |
| auto bd = filterBidirectional!"true"([1, 2, 3]); |
| assert(bd.dropExactly(2).equal([3])); |
| assert(bd.dropBackExactly(2).equal([1])); |
| } |
| |
| /++ |
| Convenience function which calls |
| $(D range.popFront()) and returns $(D range). $(D dropOne) |
| makes it easier to pop an element from a range |
| and then pass it to another function within a single expression, |
| whereas $(D popFront) would require multiple statements. |
| |
| $(D dropBackOne) provides the same functionality but instead calls |
| $(D range.popBack()). |
| +/ |
| R dropOne(R)(R range) |
| if (isInputRange!R) |
| { |
| range.popFront(); |
| return range; |
| } |
| /// ditto |
| R dropBackOne(R)(R range) |
| if (isBidirectionalRange!R) |
| { |
| range.popBack(); |
| return range; |
| } |
| |
| /// |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : filterBidirectional; |
| import std.container.dlist : DList; |
| |
| auto dl = DList!int(9, 1, 2, 3, 9); |
| assert(dl[].dropOne().dropBackOne().equal([1, 2, 3])); |
| |
| auto a = [1, 2, 3]; |
| assert(a.dropOne() == [2, 3]); |
| assert(a.dropBackOne() == [1, 2]); |
| |
| string s = "日本語"; |
| import std.exception : assumeWontThrow; |
| assert(assumeWontThrow(s.dropOne() == "本語")); |
| assert(assumeWontThrow(s.dropBackOne() == "日本")); |
| |
| auto bd = filterBidirectional!"true"([1, 2, 3]); |
| assert(bd.dropOne().equal([2, 3])); |
| assert(bd.dropBackOne().equal([1, 2])); |
| } |
| |
| /** |
| Create a range which repeats one value forever. |
| |
| Params: |
| value = the value to repeat |
| |
| Returns: |
| An infinite random access range with slicing. |
| */ |
| struct Repeat(T) |
| { |
| private: |
| //Store a non-qualified T when possible: This is to make Repeat assignable |
| static if ((is(T == class) || is(T == interface)) && (is(T == const) || is(T == immutable))) |
| { |
| import std.typecons : Rebindable; |
| alias UT = Rebindable!T; |
| } |
| else static if (is(T : Unqual!T) && is(Unqual!T : T)) |
| alias UT = Unqual!T; |
| else |
| alias UT = T; |
| UT _value; |
| |
| public: |
| /// Range primitives |
| @property inout(T) front() inout { return _value; } |
| |
| /// ditto |
| @property inout(T) back() inout { return _value; } |
| |
| /// ditto |
| enum bool empty = false; |
| |
| /// ditto |
| void popFront() {} |
| |
| /// ditto |
| void popBack() {} |
| |
| /// ditto |
| @property auto save() inout { return this; } |
| |
| /// ditto |
| inout(T) opIndex(size_t) inout { return _value; } |
| |
| /// ditto |
| auto opSlice(size_t i, size_t j) |
| in |
| { |
| assert( |
| i <= j, |
| "Attempting to slice a Repeat with a larger first argument than the second." |
| ); |
| } |
| body |
| { |
| return this.takeExactly(j - i); |
| } |
| private static struct DollarToken {} |
| |
| /// ditto |
| enum opDollar = DollarToken.init; |
| |
| /// ditto |
| auto opSlice(size_t, DollarToken) inout { return this; } |
| } |
| |
| /// Ditto |
| Repeat!T repeat(T)(T value) { return Repeat!T(value); } |
| |
| /// |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| assert(equal(5.repeat().take(4), [ 5, 5, 5, 5 ])); |
| } |
| |
| pure @safe nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| auto r = repeat(5); |
| alias R = typeof(r); |
| static assert(isBidirectionalRange!R); |
| static assert(isForwardRange!R); |
| static assert(isInfinite!R); |
| static assert(hasSlicing!R); |
| |
| assert(r.back == 5); |
| assert(r.front == 5); |
| assert(r.take(4).equal([ 5, 5, 5, 5 ])); |
| assert(r[0 .. 4].equal([ 5, 5, 5, 5 ])); |
| |
| R r2 = r[5 .. $]; |
| assert(r2.back == 5); |
| assert(r2.front == 5); |
| } |
| |
| /** |
| Repeats $(D value) exactly $(D n) times. Equivalent to $(D |
| take(repeat(value), n)). |
| */ |
| Take!(Repeat!T) repeat(T)(T value, size_t n) |
| { |
| return take(repeat(value), n); |
| } |
| |
| /// |
| pure @safe nothrow @nogc unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| assert(equal(5.repeat(4), 5.repeat().take(4))); |
| } |
| |
| pure @safe nothrow unittest //12007 |
| { |
| static class C{} |
| Repeat!(immutable int) ri; |
| ri = ri.save; |
| Repeat!(immutable C) rc; |
| rc = rc.save; |
| |
| import std.algorithm.setops : cartesianProduct; |
| import std.algorithm.comparison : equal; |
| import std.typecons : tuple; |
| immutable int[] A = [1,2,3]; |
| immutable int[] B = [4,5,6]; |
| |
| assert(equal(cartesianProduct(A,B), |
| [ |
| tuple(1, 4), tuple(1, 5), tuple(1, 6), |
| tuple(2, 4), tuple(2, 5), tuple(2, 6), |
| tuple(3, 4), tuple(3, 5), tuple(3, 6), |
| ])); |
| } |
| |
| /** |
| Given callable ($(REF isCallable, std,traits)) `fun`, create as a range |
| whose front is defined by successive calls to `fun()`. |
| This is especially useful to call function with global side effects (random |
| functions), or to create ranges expressed as a single delegate, rather than |
| an entire `front`/`popFront`/`empty` structure. |
| `fun` maybe be passed either a template alias parameter (existing |
| function, delegate, struct type defining `static opCall`) or |
| a run-time value argument (delegate, function object). |
| The result range models an InputRange |
| ($(REF isInputRange, std,range,primitives)). |
| The resulting range will call `fun()` on construction, and every call to |
| `popFront`, and the cached value will be returned when `front` is called. |
| |
| Returns: an `inputRange` where each element represents another call to fun. |
| */ |
| auto generate(Fun)(Fun fun) |
| if (isCallable!fun) |
| { |
| auto gen = Generator!(Fun)(fun); |
| gen.popFront(); // prime the first element |
| return gen; |
| } |
| |
| /// ditto |
| auto generate(alias fun)() |
| if (isCallable!fun) |
| { |
| auto gen = Generator!(fun)(); |
| gen.popFront(); // prime the first element |
| return gen; |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : map; |
| |
| int i = 1; |
| auto powersOfTwo = generate!(() => i *= 2)().take(10); |
| assert(equal(powersOfTwo, iota(1, 11).map!"2^^a"())); |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| //Returns a run-time delegate |
| auto infiniteIota(T)(T low, T high) |
| { |
| T i = high; |
| return (){if (i == high) i = low; return i++;}; |
| } |
| //adapted as a range. |
| assert(equal(generate(infiniteIota(1, 4)).take(10), [1, 2, 3, 1, 2, 3, 1, 2, 3, 1])); |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.format : format; |
| import std.random : uniform; |
| |
| auto r = generate!(() => uniform(0, 6)).take(10); |
| format("%(%s %)", r); |
| } |
| |
| private struct Generator(Fun...) |
| { |
| static assert(Fun.length == 1); |
| static assert(isInputRange!Generator); |
| |
| private: |
| static if (is(Fun[0])) |
| Fun[0] fun; |
| else |
| alias fun = Fun[0]; |
| |
| enum returnByRef_ = (functionAttributes!fun & FunctionAttribute.ref_) ? true : false; |
| static if (returnByRef_) |
| ReturnType!fun *elem_; |
| else |
| ReturnType!fun elem_; |
| public: |
| /// Range primitives |
| enum empty = false; |
| |
| static if (returnByRef_) |
| { |
| /// ditto |
| ref front() @property |
| { |
| return *elem_; |
| } |
| /// ditto |
| void popFront() |
| { |
| elem_ = &fun(); |
| } |
| } |
| else |
| { |
| /// ditto |
| auto front() @property |
| { |
| return elem_; |
| } |
| /// ditto |
| void popFront() |
| { |
| elem_ = fun(); |
| } |
| } |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| struct StaticOpCall |
| { |
| static ubyte opCall() { return 5 ; } |
| } |
| |
| assert(equal(generate!StaticOpCall().take(10), repeat(5).take(10))); |
| } |
| |
| @safe pure unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| struct OpCall |
| { |
| ubyte opCall() @safe pure { return 5 ; } |
| } |
| |
| OpCall op; |
| assert(equal(generate(op).take(10), repeat(5).take(10))); |
| } |
| |
| // verify ref mechanism works |
| @system unittest |
| { |
| int[10] arr; |
| int idx; |
| |
| ref int fun() { |
| auto x = idx++; |
| idx %= arr.length; |
| return arr[x]; |
| } |
| int y = 1; |
| foreach (ref x; generate!(fun).take(20)) |
| { |
| x += y++; |
| } |
| import std.algorithm.comparison : equal; |
| assert(equal(arr[], iota(12, 32, 2))); |
| } |
| |
| // assure front isn't the mechanism to make generate go to the next element. |
| @safe unittest |
| { |
| int i; |
| auto g = generate!(() => ++i); |
| auto f = g.front; |
| assert(f == g.front); |
| g = g.drop(5); // reassign because generate caches |
| assert(g.front == f + 5); |
| } |
| |
| /** |
| Repeats the given forward range ad infinitum. If the original range is |
| infinite (fact that would make $(D Cycle) the identity application), |
| $(D Cycle) detects that and aliases itself to the range type |
| itself. That works for non-forward ranges too. |
| If the original range has random access, $(D Cycle) offers |
| random access and also offers a constructor taking an initial position |
| $(D index). $(D Cycle) works with static arrays in addition to ranges, |
| mostly for performance reasons. |
| |
| Note: The input range must not be empty. |
| |
| Tip: This is a great way to implement simple circular buffers. |
| */ |
| struct Cycle(R) |
| if (isForwardRange!R && !isInfinite!R) |
| { |
| static if (isRandomAccessRange!R && hasLength!R) |
| { |
| private R _original; |
| private size_t _index; |
| |
| /// Range primitives |
| this(R input, size_t index = 0) |
| { |
| _original = input; |
| _index = index % _original.length; |
| } |
| |
| /// ditto |
| @property auto ref front() |
| { |
| return _original[_index]; |
| } |
| |
| static if (is(typeof((cast(const R)_original)[_index]))) |
| { |
| /// ditto |
| @property auto ref front() const |
| { |
| return _original[_index]; |
| } |
| } |
| |
| static if (hasAssignableElements!R) |
| { |
| /// ditto |
| @property void front(ElementType!R val) |
| { |
| _original[_index] = val; |
| } |
| } |
| |
| /// ditto |
| enum bool empty = false; |
| |
| /// ditto |
| void popFront() |
| { |
| ++_index; |
| if (_index >= _original.length) |
| _index = 0; |
| } |
| |
| /// ditto |
| auto ref opIndex(size_t n) |
| { |
| return _original[(n + _index) % _original.length]; |
| } |
| |
| static if (is(typeof((cast(const R)_original)[_index])) && |
| is(typeof((cast(const R)_original).length))) |
| { |
| /// ditto |
| auto ref opIndex(size_t n) const |
| { |
| return _original[(n + _index) % _original.length]; |
| } |
| } |
| |
| static if (hasAssignableElements!R) |
| { |
| /// ditto |
| void opIndexAssign(ElementType!R val, size_t n) |
| { |
| _original[(n + _index) % _original.length] = val; |
| } |
| } |
| |
| /// ditto |
| @property Cycle save() |
| { |
| //No need to call _original.save, because Cycle never actually modifies _original |
| return Cycle(_original, _index); |
| } |
| |
| private static struct DollarToken {} |
| |
| /// ditto |
| enum opDollar = DollarToken.init; |
| |
| static if (hasSlicing!R) |
| { |
| /// ditto |
| auto opSlice(size_t i, size_t j) |
| in |
| { |
| assert(i <= j); |
| } |
| body |
| { |
| return this[i .. $].takeExactly(j - i); |
| } |
| |
| /// ditto |
| auto opSlice(size_t i, DollarToken) |
| { |
| return typeof(this)(_original, _index + i); |
| } |
| } |
| } |
| else |
| { |
| private R _original; |
| private R _current; |
| |
| /// ditto |
| this(R input) |
| { |
| _original = input; |
| _current = input.save; |
| } |
| |
| /// ditto |
| @property auto ref front() |
| { |
| return _current.front; |
| } |
| |
| static if (is(typeof((cast(const R)_current).front))) |
| { |
| /// ditto |
| @property auto ref front() const |
| { |
| return _current.front; |
| } |
| } |
| |
| static if (hasAssignableElements!R) |
| { |
| /// ditto |
| @property auto front(ElementType!R val) |
| { |
| return _current.front = val; |
| } |
| } |
| |
| /// ditto |
| enum bool empty = false; |
| |
| /// ditto |
| void popFront() |
| { |
| _current.popFront(); |
| if (_current.empty) |
| _current = _original.save; |
| } |
| |
| /// ditto |
| @property Cycle save() |
| { |
| //No need to call _original.save, because Cycle never actually modifies _original |
| Cycle ret = this; |
| ret._original = _original; |
| ret._current = _current.save; |
| return ret; |
| } |
| } |
| } |
| |
| /// ditto |
| template Cycle(R) |
| if (isInfinite!R) |
| { |
| alias Cycle = R; |
| } |
| |
| /// |
| struct Cycle(R) |
| if (isStaticArray!R) |
| { |
| private alias ElementType = typeof(R.init[0]); |
| private ElementType* _ptr; |
| private size_t _index; |
| |
| nothrow: |
| |
| /// Range primitives |
| this(ref R input, size_t index = 0) @system |
| { |
| _ptr = input.ptr; |
| _index = index % R.length; |
| } |
| |
| /// ditto |
| @property ref inout(ElementType) front() inout @safe |
| { |
| static ref auto trustedPtrIdx(typeof(_ptr) p, size_t idx) @trusted |
| { |
| return p[idx]; |
| } |
| return trustedPtrIdx(_ptr, _index); |
| } |
| |
| /// ditto |
| enum bool empty = false; |
| |
| /// ditto |
| void popFront() @safe |
| { |
| ++_index; |
| if (_index >= R.length) |
| _index = 0; |
| } |
| |
| /// ditto |
| ref inout(ElementType) opIndex(size_t n) inout @safe |
| { |
| static ref auto trustedPtrIdx(typeof(_ptr) p, size_t idx) @trusted |
| { |
| return p[idx % R.length]; |
| } |
| return trustedPtrIdx(_ptr, n + _index); |
| } |
| |
| /// ditto |
| @property inout(Cycle) save() inout @safe |
| { |
| return this; |
| } |
| |
| private static struct DollarToken {} |
| /// ditto |
| enum opDollar = DollarToken.init; |
| |
| /// ditto |
| auto opSlice(size_t i, size_t j) @safe |
| in |
| { |
| assert( |
| i <= j, |
| "Attempting to slice a Repeat with a larger first argument than the second." |
| ); |
| } |
| body |
| { |
| return this[i .. $].takeExactly(j - i); |
| } |
| |
| /// ditto |
| inout(typeof(this)) opSlice(size_t i, DollarToken) inout @safe |
| { |
| static auto trustedCtor(typeof(_ptr) p, size_t idx) @trusted |
| { |
| return cast(inout) Cycle(*cast(R*)(p), idx); |
| } |
| return trustedCtor(_ptr, _index + i); |
| } |
| } |
| |
| /// Ditto |
| auto cycle(R)(R input) |
| if (isInputRange!R) |
| { |
| static assert(isForwardRange!R || isInfinite!R, |
| "Cycle requires a forward range argument unless it's statically known" |
| ~ " to be infinite"); |
| assert(!input.empty, "Attempting to pass an empty input to cycle"); |
| static if (isInfinite!R) return input; |
| else return Cycle!R(input); |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.range : cycle, take; |
| |
| // Here we create an infinitive cyclic sequence from [1, 2] |
| // (i.e. get here [1, 2, 1, 2, 1, 2 and so on]) then |
| // take 5 elements of this sequence (so we have [1, 2, 1, 2, 1]) |
| // and compare them with the expected values for equality. |
| assert(cycle([1, 2]).take(5).equal([ 1, 2, 1, 2, 1 ])); |
| } |
| |
| /// Ditto |
| Cycle!R cycle(R)(R input, size_t index = 0) |
| if (isRandomAccessRange!R && !isInfinite!R) |
| { |
| assert(!input.empty, "Attempting to pass an empty input to cycle"); |
| return Cycle!R(input, index); |
| } |
| |
| /// Ditto |
| Cycle!R cycle(R)(ref R input, size_t index = 0) @system |
| if (isStaticArray!R) |
| { |
| return Cycle!R(input, index); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.internal.test.dummyrange : AllDummyRanges; |
| |
| static assert(isForwardRange!(Cycle!(uint[]))); |
| |
| // Make sure ref is getting propagated properly. |
| int[] nums = [1,2,3]; |
| auto c2 = cycle(nums); |
| c2[3]++; |
| assert(nums[0] == 2); |
| |
| immutable int[] immarr = [1, 2, 3]; |
| |
| foreach (DummyType; AllDummyRanges) |
| { |
| static if (isForwardRange!DummyType) |
| { |
| DummyType dummy; |
| auto cy = cycle(dummy); |
| static assert(isForwardRange!(typeof(cy))); |
| auto t = take(cy, 20); |
| assert(equal(t, [1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10])); |
| |
| const cRange = cy; |
| assert(cRange.front == 1); |
| |
| static if (hasAssignableElements!DummyType) |
| { |
| { |
| cy.front = 66; |
| scope(exit) cy.front = 1; |
| assert(dummy.front == 66); |
| } |
| |
| static if (isRandomAccessRange!DummyType) |
| { |
| { |
| cy[10] = 66; |
| scope(exit) cy[10] = 1; |
| assert(dummy.front == 66); |
| } |
| |
| assert(cRange[10] == 1); |
| } |
| } |
| |
| static if (hasSlicing!DummyType) |
| { |
| auto slice = cy[5 .. 15]; |
| assert(equal(slice, [6, 7, 8, 9, 10, 1, 2, 3, 4, 5])); |
| static assert(is(typeof(slice) == typeof(takeExactly(cy, 5)))); |
| |
| auto infSlice = cy[7 .. $]; |
| assert(equal(take(infSlice, 5), [8, 9, 10, 1, 2])); |
| static assert(isInfinite!(typeof(infSlice))); |
| } |
| } |
| } |
| } |
| |
| @system unittest // For static arrays. |
| { |
| import std.algorithm.comparison : equal; |
| |
| int[3] a = [ 1, 2, 3 ]; |
| static assert(isStaticArray!(typeof(a))); |
| auto c = cycle(a); |
| assert(a.ptr == c._ptr); |
| assert(equal(take(cycle(a), 5), [ 1, 2, 3, 1, 2 ][])); |
| static assert(isForwardRange!(typeof(c))); |
| |
| // Test qualifiers on slicing. |
| alias C = typeof(c); |
| static assert(is(typeof(c[1 .. $]) == C)); |
| const cConst = c; |
| static assert(is(typeof(cConst[1 .. $]) == const(C))); |
| } |
| |
| @safe unittest // For infinite ranges |
| { |
| struct InfRange |
| { |
| void popFront() { } |
| @property int front() { return 0; } |
| enum empty = false; |
| auto save() { return this; } |
| } |
| struct NonForwardInfRange |
| { |
| void popFront() { } |
| @property int front() { return 0; } |
| enum empty = false; |
| } |
| |
| InfRange i; |
| NonForwardInfRange j; |
| auto c = cycle(i); |
| assert(c == i); |
| //make sure it can alias out even non-forward infinite ranges |
| static assert(is(typeof(j.cycle) == typeof(j))); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| int[5] arr = [0, 1, 2, 3, 4]; |
| auto cleD = cycle(arr[]); //Dynamic |
| assert(equal(cleD[5 .. 10], arr[])); |
| |
| //n is a multiple of 5 worth about 3/4 of size_t.max |
| auto n = size_t.max/4 + size_t.max/2; |
| n -= n % 5; |
| |
| //Test index overflow |
| foreach (_ ; 0 .. 10) |
| { |
| cleD = cleD[n .. $]; |
| assert(equal(cleD[5 .. 10], arr[])); |
| } |
| } |
| |
| @system unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| int[5] arr = [0, 1, 2, 3, 4]; |
| auto cleS = cycle(arr); //Static |
| assert(equal(cleS[5 .. 10], arr[])); |
| |
| //n is a multiple of 5 worth about 3/4 of size_t.max |
| auto n = size_t.max/4 + size_t.max/2; |
| n -= n % 5; |
| |
| //Test index overflow |
| foreach (_ ; 0 .. 10) |
| { |
| cleS = cleS[n .. $]; |
| assert(equal(cleS[5 .. 10], arr[])); |
| } |
| } |
| |
| @system unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| int[1] arr = [0]; |
| auto cleS = cycle(arr); |
| cleS = cleS[10 .. $]; |
| assert(equal(cleS[5 .. 10], 0.repeat(5))); |
| assert(cleS.front == 0); |
| } |
| |
| @system unittest //10845 |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : filter; |
| |
| auto a = inputRangeObject(iota(3).filter!"true"); |
| assert(equal(cycle(a).take(10), [0, 1, 2, 0, 1, 2, 0, 1, 2, 0])); |
| } |
| |
| @safe unittest // 12177 |
| { |
| static assert(__traits(compiles, recurrence!q{a[n - 1] ~ a[n - 2]}("1", "0"))); |
| } |
| |
| // Issue 13390 |
| @system unittest |
| { |
| import core.exception : AssertError; |
| import std.exception : assertThrown; |
| assertThrown!AssertError(cycle([0, 1, 2][0 .. 0])); |
| } |
| |
| private alias lengthType(R) = typeof(R.init.length.init); |
| |
| /** |
| Iterate several ranges in lockstep. The element type is a proxy tuple |
| that allows accessing the current element in the $(D n)th range by |
| using $(D e[n]). |
| |
| `zip` is similar to $(LREF lockstep), but `lockstep` doesn't |
| bundle its elements and uses the `opApply` protocol. |
| `lockstep` allows reference access to the elements in |
| `foreach` iterations. |
| |
| Params: |
| sp = controls what `zip` will do if the _ranges are different lengths |
| ranges = the ranges to zip together |
| Returns: |
| At minimum, an input range. `Zip` offers the lowest range facilities |
| of all components, e.g. it offers random access iff all ranges offer |
| random access, and also offers mutation and swapping if all ranges offer |
| it. Due to this, `Zip` is extremely powerful because it allows manipulating |
| several ranges in lockstep. |
| Throws: |
| An `Exception` if all of the _ranges are not the same length and |
| `sp` is set to `StoppingPolicy.requireSameLength`. |
| */ |
| struct Zip(Ranges...) |
| if (Ranges.length && allSatisfy!(isInputRange, Ranges)) |
| { |
| import std.format : format; //for generic mixins |
| import std.typecons : Tuple; |
| |
| alias R = Ranges; |
| private R ranges; |
| alias ElementType = Tuple!(staticMap!(.ElementType, R)); |
| private StoppingPolicy stoppingPolicy = StoppingPolicy.shortest; |
| |
| /** |
| Builds an object. Usually this is invoked indirectly by using the |
| $(LREF zip) function. |
| */ |
| this(R rs, StoppingPolicy s = StoppingPolicy.shortest) |
| { |
| ranges[] = rs[]; |
| stoppingPolicy = s; |
| } |
| |
| /** |
| Returns $(D true) if the range is at end. The test depends on the |
| stopping policy. |
| */ |
| static if (allSatisfy!(isInfinite, R)) |
| { |
| // BUG: Doesn't propagate infiniteness if only some ranges are infinite |
| // and s == StoppingPolicy.longest. This isn't fixable in the |
| // current design since StoppingPolicy is known only at runtime. |
| enum bool empty = false; |
| } |
| else |
| { |
| /// |
| @property bool empty() |
| { |
| import std.exception : enforce; |
| import std.meta : anySatisfy; |
| |
| final switch (stoppingPolicy) |
| { |
| case StoppingPolicy.shortest: |
| foreach (i, Unused; R) |
| { |
| if (ranges[i].empty) return true; |
| } |
| return false; |
| case StoppingPolicy.longest: |
| static if (anySatisfy!(isInfinite, R)) |
| { |
| return false; |
| } |
| else |
| { |
| foreach (i, Unused; R) |
| { |
| if (!ranges[i].empty) return false; |
| } |
| return true; |
| } |
| case StoppingPolicy.requireSameLength: |
| foreach (i, Unused; R[1 .. $]) |
| { |
| enforce(ranges[0].empty == |
| ranges[i + 1].empty, |
| "Inequal-length ranges passed to Zip"); |
| } |
| return ranges[0].empty; |
| } |
| assert(false); |
| } |
| } |
| |
| static if (allSatisfy!(isForwardRange, R)) |
| { |
| /// |
| @property Zip save() |
| { |
| //Zip(ranges[0].save, ranges[1].save, ..., stoppingPolicy) |
| return mixin (q{Zip(%(ranges[%s].save%|, %), stoppingPolicy)}.format(iota(0, R.length))); |
| } |
| } |
| |
| private .ElementType!(R[i]) tryGetInit(size_t i)() |
| { |
| alias E = .ElementType!(R[i]); |
| static if (!is(typeof({static E i;}))) |
| throw new Exception("Range with non-default constructable elements exhausted."); |
| else |
| return E.init; |
| } |
| |
| /** |
| Returns the current iterated element. |
| */ |
| @property ElementType front() |
| { |
| @property tryGetFront(size_t i)(){return ranges[i].empty ? tryGetInit!i() : ranges[i].front;} |
| //ElementType(tryGetFront!0, tryGetFront!1, ...) |
| return mixin(q{ElementType(%(tryGetFront!%s, %))}.format(iota(0, R.length))); |
| } |
| |
| /** |
| Sets the front of all iterated ranges. |
| */ |
| static if (allSatisfy!(hasAssignableElements, R)) |
| { |
| @property void front(ElementType v) |
| { |
| foreach (i, Unused; R) |
| { |
| if (!ranges[i].empty) |
| { |
| ranges[i].front = v[i]; |
| } |
| } |
| } |
| } |
| |
| /** |
| Moves out the front. |
| */ |
| static if (allSatisfy!(hasMobileElements, R)) |
| { |
| ElementType moveFront() |
| { |
| @property tryMoveFront(size_t i)(){return ranges[i].empty ? tryGetInit!i() : ranges[i].moveFront();} |
| //ElementType(tryMoveFront!0, tryMoveFront!1, ...) |
| return mixin(q{ElementType(%(tryMoveFront!%s, %))}.format(iota(0, R.length))); |
| } |
| } |
| |
| /** |
| Returns the rightmost element. |
| */ |
| static if (allSatisfy!(isBidirectionalRange, R)) |
| { |
| @property ElementType back() |
| { |
| //TODO: Fixme! BackElement != back of all ranges in case of jagged-ness |
| |
| @property tryGetBack(size_t i)(){return ranges[i].empty ? tryGetInit!i() : ranges[i].back;} |
| //ElementType(tryGetBack!0, tryGetBack!1, ...) |
| return mixin(q{ElementType(%(tryGetBack!%s, %))}.format(iota(0, R.length))); |
| } |
| |
| /** |
| Moves out the back. |
| */ |
| static if (allSatisfy!(hasMobileElements, R)) |
| { |
| ElementType moveBack() |
| { |
| //TODO: Fixme! BackElement != back of all ranges in case of jagged-ness |
| |
| @property tryMoveBack(size_t i)(){return ranges[i].empty ? tryGetInit!i() : ranges[i].moveFront();} |
| //ElementType(tryMoveBack!0, tryMoveBack!1, ...) |
| return mixin(q{ElementType(%(tryMoveBack!%s, %))}.format(iota(0, R.length))); |
| } |
| } |
| |
| /** |
| Returns the current iterated element. |
| */ |
| static if (allSatisfy!(hasAssignableElements, R)) |
| { |
| @property void back(ElementType v) |
| { |
| //TODO: Fixme! BackElement != back of all ranges in case of jagged-ness. |
| //Not sure the call is even legal for StoppingPolicy.longest |
| |
| foreach (i, Unused; R) |
| { |
| if (!ranges[i].empty) |
| { |
| ranges[i].back = v[i]; |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| Advances to the next element in all controlled ranges. |
| */ |
| void popFront() |
| { |
| import std.exception : enforce; |
| |
| final switch (stoppingPolicy) |
| { |
| case StoppingPolicy.shortest: |
| foreach (i, Unused; R) |
| { |
| assert(!ranges[i].empty); |
| ranges[i].popFront(); |
| } |
| break; |
| case StoppingPolicy.longest: |
| foreach (i, Unused; R) |
| { |
| if (!ranges[i].empty) ranges[i].popFront(); |
| } |
| break; |
| case StoppingPolicy.requireSameLength: |
| foreach (i, Unused; R) |
| { |
| enforce(!ranges[i].empty, "Invalid Zip object"); |
| ranges[i].popFront(); |
| } |
| break; |
| } |
| } |
| |
| /** |
| Calls $(D popBack) for all controlled ranges. |
| */ |
| static if (allSatisfy!(isBidirectionalRange, R)) |
| { |
| void popBack() |
| { |
| //TODO: Fixme! In case of jaggedness, this is wrong. |
| import std.exception : enforce; |
| |
| final switch (stoppingPolicy) |
| { |
| case StoppingPolicy.shortest: |
| foreach (i, Unused; R) |
| { |
| assert(!ranges[i].empty); |
| ranges[i].popBack(); |
| } |
| break; |
| case StoppingPolicy.longest: |
| foreach (i, Unused; R) |
| { |
| if (!ranges[i].empty) ranges[i].popBack(); |
| } |
| break; |
| case StoppingPolicy.requireSameLength: |
| foreach (i, Unused; R) |
| { |
| enforce(!ranges[i].empty, "Invalid Zip object"); |
| ranges[i].popBack(); |
| } |
| break; |
| } |
| } |
| } |
| |
| /** |
| Returns the length of this range. Defined only if all ranges define |
| $(D length). |
| */ |
| static if (allSatisfy!(hasLength, R)) |
| { |
| @property auto length() |
| { |
| static if (Ranges.length == 1) |
| return ranges[0].length; |
| else |
| { |
| if (stoppingPolicy == StoppingPolicy.requireSameLength) |
| return ranges[0].length; |
| |
| //[min|max](ranges[0].length, ranges[1].length, ...) |
| import std.algorithm.comparison : min, max; |
| if (stoppingPolicy == StoppingPolicy.shortest) |
| return mixin(q{min(%(ranges[%s].length%|, %))}.format(iota(0, R.length))); |
| else |
| return mixin(q{max(%(ranges[%s].length%|, %))}.format(iota(0, R.length))); |
| } |
| } |
| |
| alias opDollar = length; |
| } |
| |
| /** |
| Returns a slice of the range. Defined only if all range define |
| slicing. |
| */ |
| static if (allSatisfy!(hasSlicing, R)) |
| { |
| auto opSlice(size_t from, size_t to) |
| { |
| //Slicing an infinite range yields the type Take!R |
| //For finite ranges, the type Take!R aliases to R |
| alias ZipResult = Zip!(staticMap!(Take, R)); |
| |
| //ZipResult(ranges[0][from .. to], ranges[1][from .. to], ..., stoppingPolicy) |
| return mixin (q{ZipResult(%(ranges[%s][from .. to]%|, %), stoppingPolicy)}.format(iota(0, R.length))); |
| } |
| } |
| |
| /** |
| Returns the $(D n)th element in the composite range. Defined if all |
| ranges offer random access. |
| */ |
| static if (allSatisfy!(isRandomAccessRange, R)) |
| { |
| ElementType opIndex(size_t n) |
| { |
| //TODO: Fixme! This may create an out of bounds access |
| //for StoppingPolicy.longest |
| |
| //ElementType(ranges[0][n], ranges[1][n], ...) |
| return mixin (q{ElementType(%(ranges[%s][n]%|, %))}.format(iota(0, R.length))); |
| } |
| |
| /** |
| Assigns to the $(D n)th element in the composite range. Defined if |
| all ranges offer random access. |
| */ |
| static if (allSatisfy!(hasAssignableElements, R)) |
| { |
| void opIndexAssign(ElementType v, size_t n) |
| { |
| //TODO: Fixme! Not sure the call is even legal for StoppingPolicy.longest |
| foreach (i, Range; R) |
| { |
| ranges[i][n] = v[i]; |
| } |
| } |
| } |
| |
| /** |
| Destructively reads the $(D n)th element in the composite |
| range. Defined if all ranges offer random access. |
| */ |
| static if (allSatisfy!(hasMobileElements, R)) |
| { |
| ElementType moveAt(size_t n) |
| { |
| //TODO: Fixme! This may create an out of bounds access |
| //for StoppingPolicy.longest |
| |
| //ElementType(ranges[0].moveAt(n), ranges[1].moveAt(n), ..., ) |
| return mixin (q{ElementType(%(ranges[%s].moveAt(n)%|, %))}.format(iota(0, R.length))); |
| } |
| } |
| } |
| } |
| |
| /// Ditto |
| auto zip(Ranges...)(Ranges ranges) |
| if (Ranges.length && allSatisfy!(isInputRange, Ranges)) |
| { |
| return Zip!Ranges(ranges); |
| } |
| |
| /// |
| pure @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : map; |
| |
| // pairwise sum |
| auto arr = [0, 1, 2]; |
| assert(zip(arr, arr.dropOne).map!"a[0] + a[1]".equal([1, 3])); |
| } |
| |
| /// |
| pure @safe unittest |
| { |
| import std.conv : to; |
| |
| int[] a = [ 1, 2, 3 ]; |
| string[] b = [ "a", "b", "c" ]; |
| string[] result; |
| |
| foreach (tup; zip(a, b)) |
| { |
| result ~= tup[0].to!string ~ tup[1]; |
| } |
| |
| assert(result == [ "1a", "2b", "3c" ]); |
| |
| size_t idx = 0; |
| // unpacking tuple elements with foreach |
| foreach (e1, e2; zip(a, b)) |
| { |
| assert(e1 == a[idx]); |
| assert(e2 == b[idx]); |
| ++idx; |
| } |
| } |
| |
| /// $(D zip) is powerful - the following code sorts two arrays in parallel: |
| pure @safe unittest |
| { |
| import std.algorithm.sorting : sort; |
| |
| int[] a = [ 1, 2, 3 ]; |
| string[] b = [ "a", "c", "b" ]; |
| zip(a, b).sort!((t1, t2) => t1[0] > t2[0]); |
| |
| assert(a == [ 3, 2, 1 ]); |
| // b is sorted according to a's sorting |
| assert(b == [ "b", "c", "a" ]); |
| } |
| |
| /// Ditto |
| auto zip(Ranges...)(StoppingPolicy sp, Ranges ranges) |
| if (Ranges.length && allSatisfy!(isInputRange, Ranges)) |
| { |
| return Zip!Ranges(ranges, sp); |
| } |
| |
| /** |
| Dictates how iteration in a $(D Zip) should stop. By default stop at |
| the end of the shortest of all ranges. |
| */ |
| enum StoppingPolicy |
| { |
| /// Stop when the shortest range is exhausted |
| shortest, |
| /// Stop when the longest range is exhausted |
| longest, |
| /// Require that all ranges are equal |
| requireSameLength, |
| } |
| |
| @system unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : filter, map; |
| import std.algorithm.mutation : swap; |
| import std.algorithm.sorting : sort; |
| |
| import std.exception : assertThrown, assertNotThrown; |
| import std.typecons : tuple; |
| |
| int[] a = [ 1, 2, 3 ]; |
| float[] b = [ 1.0, 2.0, 3.0 ]; |
| foreach (e; zip(a, b)) |
| { |
| assert(e[0] == e[1]); |
| } |
| |
| swap(a[0], a[1]); |
| auto z = zip(a, b); |
| //swap(z.front(), z.back()); |
| sort!("a[0] < b[0]")(zip(a, b)); |
| assert(a == [1, 2, 3]); |
| assert(b == [2.0, 1.0, 3.0]); |
| |
| z = zip(StoppingPolicy.requireSameLength, a, b); |
| assertNotThrown(z.popBack()); |
| assertNotThrown(z.popBack()); |
| assertNotThrown(z.popBack()); |
| assert(z.empty); |
| assertThrown(z.popBack()); |
| |
| a = [ 1, 2, 3 ]; |
| b = [ 1.0, 2.0, 3.0 ]; |
| sort!("a[0] > b[0]")(zip(StoppingPolicy.requireSameLength, a, b)); |
| assert(a == [3, 2, 1]); |
| assert(b == [3.0, 2.0, 1.0]); |
| |
| a = []; |
| b = []; |
| assert(zip(StoppingPolicy.requireSameLength, a, b).empty); |
| |
| // Test infiniteness propagation. |
| static assert(isInfinite!(typeof(zip(repeat(1), repeat(1))))); |
| |
| // Test stopping policies with both value and reference. |
| auto a1 = [1, 2]; |
| auto a2 = [1, 2, 3]; |
| auto stuff = tuple(tuple(a1, a2), |
| tuple(filter!"a"(a1), filter!"a"(a2))); |
| |
| alias FOO = Zip!(immutable(int)[], immutable(float)[]); |
| |
| foreach (t; stuff.expand) |
| { |
| auto arr1 = t[0]; |
| auto arr2 = t[1]; |
| auto zShortest = zip(arr1, arr2); |
| assert(equal(map!"a[0]"(zShortest), [1, 2])); |
| assert(equal(map!"a[1]"(zShortest), [1, 2])); |
| |
| try { |
| auto zSame = zip(StoppingPolicy.requireSameLength, arr1, arr2); |
| foreach (elem; zSame) {} |
| assert(0); |
| } catch (Throwable) { /* It's supposed to throw.*/ } |
| |
| auto zLongest = zip(StoppingPolicy.longest, arr1, arr2); |
| assert(!zLongest.ranges[0].empty); |
| assert(!zLongest.ranges[1].empty); |
| |
| zLongest.popFront(); |
| zLongest.popFront(); |
| assert(!zLongest.empty); |
| assert(zLongest.ranges[0].empty); |
| assert(!zLongest.ranges[1].empty); |
| |
| zLongest.popFront(); |
| assert(zLongest.empty); |
| } |
| |
| // BUG 8900 |
| assert(zip([1, 2], repeat('a')).array == [tuple(1, 'a'), tuple(2, 'a')]); |
| assert(zip(repeat('a'), [1, 2]).array == [tuple('a', 1), tuple('a', 2)]); |
| |
| // Doesn't work yet. Issues w/ emplace. |
| // static assert(is(Zip!(immutable int[], immutable float[]))); |
| |
| |
| // These unittests pass, but make the compiler consume an absurd amount |
| // of RAM and time. Therefore, they should only be run if explicitly |
| // uncommented when making changes to Zip. Also, running them using |
| // make -fwin32.mak unittest makes the compiler completely run out of RAM. |
| // You need to test just this module. |
| /+ |
| foreach (DummyType1; AllDummyRanges) |
| { |
| DummyType1 d1; |
| foreach (DummyType2; AllDummyRanges) |
| { |
| DummyType2 d2; |
| auto r = zip(d1, d2); |
| assert(equal(map!"a[0]"(r), [1,2,3,4,5,6,7,8,9,10])); |
| assert(equal(map!"a[1]"(r), [1,2,3,4,5,6,7,8,9,10])); |
| |
| static if (isForwardRange!DummyType1 && isForwardRange!DummyType2) |
| { |
| static assert(isForwardRange!(typeof(r))); |
| } |
| |
| static if (isBidirectionalRange!DummyType1 && |
| isBidirectionalRange!DummyType2) { |
| static assert(isBidirectionalRange!(typeof(r))); |
| } |
| static if (isRandomAccessRange!DummyType1 && |
| isRandomAccessRange!DummyType2) { |
| static assert(isRandomAccessRange!(typeof(r))); |
| } |
| } |
| } |
| +/ |
| } |
| |
| pure @safe unittest |
| { |
| import std.algorithm.sorting : sort; |
| |
| auto a = [5,4,3,2,1]; |
| auto b = [3,1,2,5,6]; |
| auto z = zip(a, b); |
| |
| sort!"a[0] < b[0]"(z); |
| |
| assert(a == [1, 2, 3, 4, 5]); |
| assert(b == [6, 5, 2, 1, 3]); |
| } |
| |
| @safe pure unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.typecons : tuple; |
| |
| auto LL = iota(1L, 1000L); |
| auto z = zip(LL, [4]); |
| |
| assert(equal(z, [tuple(1L,4)])); |
| |
| auto LL2 = iota(0L, 500L); |
| auto z2 = zip([7], LL2); |
| assert(equal(z2, [tuple(7, 0L)])); |
| } |
| |
| // Text for Issue 11196 |
| @safe pure unittest |
| { |
| import std.exception : assertThrown; |
| |
| static struct S { @disable this(); } |
| assert(zip((S[5]).init[]).length == 5); |
| assert(zip(StoppingPolicy.longest, cast(S[]) null, new int[1]).length == 1); |
| assertThrown(zip(StoppingPolicy.longest, cast(S[]) null, new int[1]).front); |
| } |
| |
| @safe pure unittest //12007 |
| { |
| static struct R |
| { |
| enum empty = false; |
| void popFront(){} |
| int front(){return 1;} @property |
| R save(){return this;} @property |
| void opAssign(R) @disable; |
| } |
| R r; |
| auto z = zip(r, r); |
| assert(z.save == z); |
| } |
| |
| pure @system unittest |
| { |
| import std.typecons : tuple; |
| |
| auto r1 = [0,1,2]; |
| auto r2 = [1,2,3]; |
| auto z1 = zip(refRange(&r1), refRange(&r2)); |
| auto z2 = z1.save; |
| z1.popFront(); |
| assert(z1.front == tuple(1,2)); |
| assert(z2.front == tuple(0,1)); |
| } |
| |
| /* |
| Generate lockstep's opApply function as a mixin string. |
| If withIndex is true prepend a size_t index to the delegate. |
| */ |
| private string lockstepMixin(Ranges...)(bool withIndex, bool reverse) |
| { |
| import std.format : format; |
| |
| string[] params; |
| string[] emptyChecks; |
| string[] dgArgs; |
| string[] popFronts; |
| string indexDef; |
| string indexInc; |
| |
| if (withIndex) |
| { |
| params ~= "size_t"; |
| dgArgs ~= "index"; |
| if (reverse) |
| { |
| indexDef = q{ |
| size_t index = ranges[0].length-1; |
| enforce(_stoppingPolicy == StoppingPolicy.requireSameLength, |
| "lockstep can only be used with foreach_reverse when stoppingPolicy == requireSameLength"); |
| |
| foreach (range; ranges[1..$]) |
| enforce(range.length == ranges[0].length); |
| }; |
| indexInc = "--index;"; |
| } |
| else |
| { |
| indexDef = "size_t index = 0;"; |
| indexInc = "++index;"; |
| } |
| } |
| |
| foreach (idx, Range; Ranges) |
| { |
| params ~= format("%sElementType!(Ranges[%s])", hasLvalueElements!Range ? "ref " : "", idx); |
| emptyChecks ~= format("!ranges[%s].empty", idx); |
| if (reverse) |
| { |
| dgArgs ~= format("ranges[%s].back", idx); |
| popFronts ~= format("ranges[%s].popBack();", idx); |
| } |
| else |
| { |
| dgArgs ~= format("ranges[%s].front", idx); |
| popFronts ~= format("ranges[%s].popFront();", idx); |
| } |
| } |
| |
| string name = reverse ? "opApplyReverse" : "opApply"; |
| |
| return format( |
| q{ |
| int %s(scope int delegate(%s) dg) |
| { |
| import std.exception : enforce; |
| |
| auto ranges = _ranges; |
| int res; |
| %s |
| |
| while (%s) |
| { |
| res = dg(%s); |
| if (res) break; |
| %s |
| %s |
| } |
| |
| if (_stoppingPolicy == StoppingPolicy.requireSameLength) |
| { |
| foreach (range; ranges) |
| enforce(range.empty); |
| } |
| return res; |
| } |
| }, name, params.join(", "), indexDef, |
| emptyChecks.join(" && "), dgArgs.join(", "), |
| popFronts.join("\n "), |
| indexInc); |
| } |
| |
| /** |
| Iterate multiple ranges in lockstep using a $(D foreach) loop. In contrast to |
| $(LREF zip) it allows reference access to its elements. If only a single |
| range is passed in, the $(D Lockstep) aliases itself away. If the |
| ranges are of different lengths and $(D s) == $(D StoppingPolicy.shortest) |
| stop after the shortest range is empty. If the ranges are of different |
| lengths and $(D s) == $(D StoppingPolicy.requireSameLength), throw an |
| exception. $(D s) may not be $(D StoppingPolicy.longest), and passing this |
| will throw an exception. |
| |
| Iterating over $(D Lockstep) in reverse and with an index is only possible |
| when $(D s) == $(D StoppingPolicy.requireSameLength), in order to preserve |
| indexes. If an attempt is made at iterating in reverse when $(D s) == |
| $(D StoppingPolicy.shortest), an exception will be thrown. |
| |
| By default $(D StoppingPolicy) is set to $(D StoppingPolicy.shortest). |
| |
| See_Also: $(LREF zip) |
| |
| `lockstep` is similar to $(LREF zip), but `zip` bundles its |
| elements and returns a range. |
| `lockstep` also supports reference access. |
| Use `zip` if you want to pass the result to a range function. |
| */ |
| struct Lockstep(Ranges...) |
| if (Ranges.length > 1 && allSatisfy!(isInputRange, Ranges)) |
| { |
| /// |
| this(R ranges, StoppingPolicy sp = StoppingPolicy.shortest) |
| { |
| import std.exception : enforce; |
| |
| _ranges = ranges; |
| enforce(sp != StoppingPolicy.longest, |
| "Can't use StoppingPolicy.Longest on Lockstep."); |
| _stoppingPolicy = sp; |
| } |
| |
| mixin(lockstepMixin!Ranges(false, false)); |
| mixin(lockstepMixin!Ranges(true, false)); |
| static if (allSatisfy!(isBidirectionalRange, Ranges)) |
| { |
| mixin(lockstepMixin!Ranges(false, true)); |
| static if (allSatisfy!(hasLength, Ranges)) |
| { |
| mixin(lockstepMixin!Ranges(true, true)); |
| } |
| else |
| { |
| mixin(lockstepReverseFailMixin!Ranges(true)); |
| } |
| } |
| else |
| { |
| mixin(lockstepReverseFailMixin!Ranges(false)); |
| mixin(lockstepReverseFailMixin!Ranges(true)); |
| } |
| |
| private: |
| alias R = Ranges; |
| R _ranges; |
| StoppingPolicy _stoppingPolicy; |
| } |
| |
| string lockstepReverseFailMixin(Ranges...)(bool withIndex) |
| { |
| import std.format : format; |
| string[] params; |
| string message; |
| |
| if (withIndex) |
| { |
| message = "Indexed reverse iteration with lockstep is only supported" |
| ~"if all ranges are bidirectional and have a length.\n"; |
| } |
| else |
| { |
| message = "Reverse iteration with lockstep is only supported if all ranges are bidirectional.\n"; |
| } |
| |
| if (withIndex) |
| { |
| params ~= "size_t"; |
| } |
| |
| foreach (idx, Range; Ranges) |
| { |
| params ~= format("%sElementType!(Ranges[%s])", hasLvalueElements!Range ? "ref " : "", idx); |
| } |
| |
| return format( |
| q{ |
| int opApplyReverse()(scope int delegate(%s) dg) |
| { |
| static assert(false, "%s"); |
| } |
| }, params.join(", "), message); |
| } |
| |
| // For generic programming, make sure Lockstep!(Range) is well defined for a |
| // single range. |
| template Lockstep(Range) |
| { |
| alias Lockstep = Range; |
| } |
| |
| /// Ditto |
| Lockstep!(Ranges) lockstep(Ranges...)(Ranges ranges) |
| if (allSatisfy!(isInputRange, Ranges)) |
| { |
| return Lockstep!(Ranges)(ranges); |
| } |
| /// Ditto |
| Lockstep!(Ranges) lockstep(Ranges...)(Ranges ranges, StoppingPolicy s) |
| if (allSatisfy!(isInputRange, Ranges)) |
| { |
| static if (Ranges.length > 1) |
| return Lockstep!Ranges(ranges, s); |
| else |
| return ranges[0]; |
| } |
| |
| /// |
| @system unittest |
| { |
| auto arr1 = [1,2,3,4,5,100]; |
| auto arr2 = [6,7,8,9,10]; |
| |
| foreach (ref a, b; lockstep(arr1, arr2)) |
| { |
| a += b; |
| } |
| |
| assert(arr1 == [7,9,11,13,15,100]); |
| |
| /// Lockstep also supports iterating with an index variable: |
| foreach (index, a, b; lockstep(arr1, arr2)) |
| { |
| assert(arr1[index] == a); |
| assert(arr2[index] == b); |
| } |
| } |
| |
| @system unittest // Bugzilla 15860: foreach_reverse on lockstep |
| { |
| auto arr1 = [0, 1, 2, 3]; |
| auto arr2 = [4, 5, 6, 7]; |
| |
| size_t n = arr1.length -1; |
| foreach_reverse (index, a, b; lockstep(arr1, arr2, StoppingPolicy.requireSameLength)) |
| { |
| assert(n == index); |
| assert(index == a); |
| assert(arr1[index] == a); |
| assert(arr2[index] == b); |
| n--; |
| } |
| |
| auto arr3 = [4, 5]; |
| n = 1; |
| foreach_reverse (a, b; lockstep(arr1, arr3)) |
| { |
| assert(a == arr1[$-n] && b == arr3[$-n]); |
| n++; |
| } |
| } |
| |
| @system unittest |
| { |
| import std.algorithm.iteration : filter; |
| import std.conv : to; |
| |
| // The filters are to make these the lowest common forward denominator ranges, |
| // i.e. w/o ref return, random access, length, etc. |
| auto foo = filter!"a"([1,2,3,4,5]); |
| immutable bar = [6f,7f,8f,9f,10f].idup; |
| auto l = lockstep(foo, bar); |
| |
| // Should work twice. These are forward ranges with implicit save. |
| foreach (i; 0 .. 2) |
| { |
| uint[] res1; |
| float[] res2; |
| |
| foreach (a, ref b; l) |
| { |
| res1 ~= a; |
| res2 ~= b; |
| } |
| |
| assert(res1 == [1,2,3,4,5]); |
| assert(res2 == [6,7,8,9,10]); |
| assert(bar == [6f,7f,8f,9f,10f]); |
| } |
| |
| // Doc example. |
| auto arr1 = [1,2,3,4,5]; |
| auto arr2 = [6,7,8,9,10]; |
| |
| foreach (ref a, ref b; lockstep(arr1, arr2)) |
| { |
| a += b; |
| } |
| |
| assert(arr1 == [7,9,11,13,15]); |
| |
| // Make sure StoppingPolicy.requireSameLength doesn't throw. |
| auto ls = lockstep(arr1, arr2, StoppingPolicy.requireSameLength); |
| |
| int k = 1; |
| foreach (a, b; ls) |
| { |
| assert(a - b == k); |
| ++k; |
| } |
| |
| // Make sure StoppingPolicy.requireSameLength throws. |
| arr2.popBack(); |
| ls = lockstep(arr1, arr2, StoppingPolicy.requireSameLength); |
| |
| try { |
| foreach (a, b; ls) {} |
| assert(0); |
| } catch (Exception) {} |
| |
| // Just make sure 1-range case instantiates. This hangs the compiler |
| // when no explicit stopping policy is specified due to Bug 4652. |
| auto stuff = lockstep([1,2,3,4,5], StoppingPolicy.shortest); |
| foreach (int i, a; stuff) |
| { |
| assert(stuff[i] == a); |
| } |
| |
| // Test with indexing. |
| uint[] res1; |
| float[] res2; |
| size_t[] indices; |
| foreach (i, a, b; lockstep(foo, bar)) |
| { |
| indices ~= i; |
| res1 ~= a; |
| res2 ~= b; |
| } |
| |
| assert(indices == to!(size_t[])([0, 1, 2, 3, 4])); |
| assert(res1 == [1,2,3,4,5]); |
| assert(res2 == [6f,7f,8f,9f,10f]); |
| |
| // Make sure we've worked around the relevant compiler bugs and this at least |
| // compiles w/ >2 ranges. |
| lockstep(foo, foo, foo); |
| |
| // Make sure it works with const. |
| const(int[])[] foo2 = [[1, 2, 3]]; |
| const(int[])[] bar2 = [[4, 5, 6]]; |
| auto c = chain(foo2, bar2); |
| |
| foreach (f, b; lockstep(c, c)) {} |
| |
| // Regression 10468 |
| foreach (x, y; lockstep(iota(0, 10), iota(0, 10))) { } |
| } |
| |
| @system unittest |
| { |
| struct RvalueRange |
| { |
| int[] impl; |
| @property bool empty() { return impl.empty; } |
| @property int front() { return impl[0]; } // N.B. non-ref |
| void popFront() { impl.popFront(); } |
| } |
| auto data1 = [ 1, 2, 3, 4 ]; |
| auto data2 = [ 5, 6, 7, 8 ]; |
| auto r1 = RvalueRange(data1); |
| auto r2 = data2; |
| foreach (a, ref b; lockstep(r1, r2)) |
| { |
| a++; |
| b++; |
| } |
| assert(data1 == [ 1, 2, 3, 4 ]); // changes to a do not propagate to data |
| assert(data2 == [ 6, 7, 8, 9 ]); // but changes to b do. |
| |
| // Since r1 is by-value only, the compiler should reject attempts to |
| // foreach over it with ref. |
| static assert(!__traits(compiles, { |
| foreach (ref a, ref b; lockstep(r1, r2)) { a++; } |
| })); |
| } |
| |
| /** |
| Creates a mathematical sequence given the initial values and a |
| recurrence function that computes the next value from the existing |
| values. The sequence comes in the form of an infinite forward |
| range. The type $(D Recurrence) itself is seldom used directly; most |
| often, recurrences are obtained by calling the function $(D |
| recurrence). |
| |
| When calling $(D recurrence), the function that computes the next |
| value is specified as a template argument, and the initial values in |
| the recurrence are passed as regular arguments. For example, in a |
| Fibonacci sequence, there are two initial values (and therefore a |
| state size of 2) because computing the next Fibonacci value needs the |
| past two values. |
| |
| The signature of this function should be: |
| ---- |
| auto fun(R)(R state, size_t n) |
| ---- |
| where $(D n) will be the index of the current value, and $(D state) will be an |
| opaque state vector that can be indexed with array-indexing notation |
| $(D state[i]), where valid values of $(D i) range from $(D (n - 1)) to |
| $(D (n - State.length)). |
| |
| If the function is passed in string form, the state has name $(D "a") |
| and the zero-based index in the recurrence has name $(D "n"). The |
| given string must return the desired value for $(D a[n]) given $(D a[n |
| - 1]), $(D a[n - 2]), $(D a[n - 3]),..., $(D a[n - stateSize]). The |
| state size is dictated by the number of arguments passed to the call |
| to $(D recurrence). The $(D Recurrence) struct itself takes care of |
| managing the recurrence's state and shifting it appropriately. |
| */ |
| struct Recurrence(alias fun, StateType, size_t stateSize) |
| { |
| import std.functional : binaryFun; |
| |
| StateType[stateSize] _state; |
| size_t _n; |
| |
| this(StateType[stateSize] initial) { _state = initial; } |
| |
| void popFront() |
| { |
| static auto trustedCycle(ref typeof(_state) s) @trusted |
| { |
| return cycle(s); |
| } |
| // The cast here is reasonable because fun may cause integer |
| // promotion, but needs to return a StateType to make its operation |
| // closed. Therefore, we have no other choice. |
| _state[_n % stateSize] = cast(StateType) binaryFun!(fun, "a", "n")( |
| trustedCycle(_state), _n + stateSize); |
| ++_n; |
| } |
| |
| @property StateType front() |
| { |
| return _state[_n % stateSize]; |
| } |
| |
| @property typeof(this) save() |
| { |
| return this; |
| } |
| |
| enum bool empty = false; |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| // The Fibonacci numbers, using function in string form: |
| // a[0] = 1, a[1] = 1, and compute a[n+1] = a[n-1] + a[n] |
| auto fib = recurrence!("a[n-1] + a[n-2]")(1, 1); |
| assert(fib.take(10).equal([1, 1, 2, 3, 5, 8, 13, 21, 34, 55])); |
| |
| // The factorials, using function in lambda form: |
| auto fac = recurrence!((a,n) => a[n-1] * n)(1); |
| assert(take(fac, 10).equal([ |
| 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 |
| ])); |
| |
| // The triangular numbers, using function in explicit form: |
| static size_t genTriangular(R)(R state, size_t n) |
| { |
| return state[n-1] + n; |
| } |
| auto tri = recurrence!genTriangular(0); |
| assert(take(tri, 10).equal([0, 1, 3, 6, 10, 15, 21, 28, 36, 45])); |
| } |
| |
| /// Ditto |
| Recurrence!(fun, CommonType!(State), State.length) |
| recurrence(alias fun, State...)(State initial) |
| { |
| CommonType!(State)[State.length] state; |
| foreach (i, Unused; State) |
| { |
| state[i] = initial[i]; |
| } |
| return typeof(return)(state); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| auto fib = recurrence!("a[n-1] + a[n-2]")(1, 1); |
| static assert(isForwardRange!(typeof(fib))); |
| |
| int[] witness = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ]; |
| assert(equal(take(fib, 10), witness)); |
| foreach (e; take(fib, 10)) {} |
| auto fact = recurrence!("n * a[n-1]")(1); |
| assert( equal(take(fact, 10), [1, 1, 2, 2*3, 2*3*4, 2*3*4*5, 2*3*4*5*6, |
| 2*3*4*5*6*7, 2*3*4*5*6*7*8, 2*3*4*5*6*7*8*9][]) ); |
| auto piapprox = recurrence!("a[n] + (n & 1 ? 4.0 : -4.0) / (2 * n + 3)")(4.0); |
| foreach (e; take(piapprox, 20)) {} |
| // Thanks to yebblies for this test and the associated fix |
| auto r = recurrence!"a[n-2]"(1, 2); |
| witness = [1, 2, 1, 2, 1]; |
| assert(equal(take(r, 5), witness)); |
| } |
| |
| /** |
| $(D Sequence) is similar to $(D Recurrence) except that iteration is |
| presented in the so-called $(HTTP en.wikipedia.org/wiki/Closed_form, |
| closed form). This means that the $(D n)th element in the series is |
| computable directly from the initial values and $(D n) itself. This |
| implies that the interface offered by $(D Sequence) is a random-access |
| range, as opposed to the regular $(D Recurrence), which only offers |
| forward iteration. |
| |
| The state of the sequence is stored as a $(D Tuple) so it can be |
| heterogeneous. |
| */ |
| struct Sequence(alias fun, State) |
| { |
| private: |
| import std.functional : binaryFun; |
| |
| alias compute = binaryFun!(fun, "a", "n"); |
| alias ElementType = typeof(compute(State.init, cast(size_t) 1)); |
| State _state; |
| size_t _n; |
| |
| static struct DollarToken{} |
| |
| public: |
| this(State initial, size_t n = 0) |
| { |
| _state = initial; |
| _n = n; |
| } |
| |
| @property ElementType front() |
| { |
| return compute(_state, _n); |
| } |
| |
| void popFront() |
| { |
| ++_n; |
| } |
| |
| enum opDollar = DollarToken(); |
| |
| auto opSlice(size_t lower, size_t upper) |
| in |
| { |
| assert( |
| upper >= lower, |
| "Attempting to slice a Sequence with a larger first argument than the second." |
| ); |
| } |
| body |
| { |
| return typeof(this)(_state, _n + lower).take(upper - lower); |
| } |
| |
| auto opSlice(size_t lower, DollarToken) |
| { |
| return typeof(this)(_state, _n + lower); |
| } |
| |
| ElementType opIndex(size_t n) |
| { |
| return compute(_state, n + _n); |
| } |
| |
| enum bool empty = false; |
| |
| @property Sequence save() { return this; } |
| } |
| |
| /// Ditto |
| auto sequence(alias fun, State...)(State args) |
| { |
| import std.typecons : Tuple, tuple; |
| alias Return = Sequence!(fun, Tuple!State); |
| return Return(tuple(args)); |
| } |
| |
| /// Odd numbers, using function in string form: |
| @safe unittest |
| { |
| auto odds = sequence!("a[0] + n * a[1]")(1, 2); |
| assert(odds.front == 1); |
| odds.popFront(); |
| assert(odds.front == 3); |
| odds.popFront(); |
| assert(odds.front == 5); |
| } |
| |
| /// Triangular numbers, using function in lambda form: |
| @safe unittest |
| { |
| auto tri = sequence!((a,n) => n*(n+1)/2)(); |
| |
| // Note random access |
| assert(tri[0] == 0); |
| assert(tri[3] == 6); |
| assert(tri[1] == 1); |
| assert(tri[4] == 10); |
| assert(tri[2] == 3); |
| } |
| |
| /// Fibonacci numbers, using function in explicit form: |
| @safe unittest |
| { |
| import std.math : pow, round, sqrt; |
| static ulong computeFib(S)(S state, size_t n) |
| { |
| // Binet's formula |
| return cast(ulong)(round((pow(state[0], n+1) - pow(state[1], n+1)) / |
| state[2])); |
| } |
| auto fib = sequence!computeFib( |
| (1.0 + sqrt(5.0)) / 2.0, // Golden Ratio |
| (1.0 - sqrt(5.0)) / 2.0, // Conjugate of Golden Ratio |
| sqrt(5.0)); |
| |
| // Note random access with [] operator |
| assert(fib[1] == 1); |
| assert(fib[4] == 5); |
| assert(fib[3] == 3); |
| assert(fib[2] == 2); |
| assert(fib[9] == 55); |
| } |
| |
| @safe unittest |
| { |
| import std.typecons : Tuple, tuple; |
| auto y = Sequence!("a[0] + n * a[1]", Tuple!(int, int))(tuple(0, 4)); |
| static assert(isForwardRange!(typeof(y))); |
| |
| //@@BUG |
| //auto y = sequence!("a[0] + n * a[1]")(0, 4); |
| //foreach (e; take(y, 15)) |
| {} //writeln(e); |
| |
| auto odds = Sequence!("a[0] + n * a[1]", Tuple!(int, int))( |
| tuple(1, 2)); |
| for (int currentOdd = 1; currentOdd <= 21; currentOdd += 2) |
| { |
| assert(odds.front == odds[0]); |
| assert(odds[0] == currentOdd); |
| odds.popFront(); |
| } |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| auto odds = sequence!("a[0] + n * a[1]")(1, 2); |
| static assert(hasSlicing!(typeof(odds))); |
| |
| //Note: don't use drop or take as the target of an equal, |
| //since they'll both just forward to opSlice, making the tests irrelevant |
| |
| // static slicing tests |
| assert(equal(odds[0 .. 5], [1, 3, 5, 7, 9])); |
| assert(equal(odds[3 .. 7], [7, 9, 11, 13])); |
| |
| // relative slicing test, testing slicing is NOT agnostic of state |
| auto odds_less5 = odds.drop(5); //this should actually call odds[5 .. $] |
| assert(equal(odds_less5[0 .. 3], [11, 13, 15])); |
| assert(equal(odds_less5[0 .. 10], odds[5 .. 15])); |
| |
| //Infinite slicing tests |
| odds = odds[10 .. $]; |
| assert(equal(odds.take(3), [21, 23, 25])); |
| } |
| |
| // Issue 5036 |
| @safe unittest |
| { |
| auto s = sequence!((a, n) => new int)(0); |
| assert(s.front != s.front); // no caching |
| } |
| |
| // iota |
| /** |
| Creates a range of values that span the given starting and stopping |
| values. |
| |
| Params: |
| begin = The starting value. |
| end = The value that serves as the stopping criterion. This value is not |
| included in the range. |
| step = The value to add to the current value at each iteration. |
| |
| Returns: |
| A range that goes through the numbers $(D begin), $(D begin + step), |
| $(D begin + 2 * step), $(D ...), up to and excluding $(D end). |
| |
| The two-argument overloads have $(D step = 1). If $(D begin < end && step < |
| 0) or $(D begin > end && step > 0) or $(D begin == end), then an empty range |
| is returned. If $(D step == 0) then $(D begin == end) is an error. |
| |
| For built-in types, the range returned is a random access range. For |
| user-defined types that support $(D ++), the range is an input |
| range. |
| |
| An integral iota also supports $(D in) operator from the right. It takes |
| the stepping into account, the integral won't be considered |
| contained if it falls between two consecutive values of the range. |
| $(D contains) does the same as in, but from lefthand side. |
| |
| Example: |
| --- |
| void main() |
| { |
| import std.stdio; |
| |
| // The following groups all produce the same output of: |
| // 0 1 2 3 4 |
| |
| foreach (i; 0 .. 5) |
| writef("%s ", i); |
| writeln(); |
| |
| import std.range : iota; |
| foreach (i; iota(0, 5)) |
| writef("%s ", i); |
| writeln(); |
| |
| writefln("%(%s %|%)", iota(0, 5)); |
| |
| import std.algorithm.iteration : map; |
| import std.algorithm.mutation : copy; |
| import std.format; |
| iota(0, 5).map!(i => format("%s ", i)).copy(stdout.lockingTextWriter()); |
| writeln(); |
| } |
| --- |
| */ |
| auto iota(B, E, S)(B begin, E end, S step) |
| if ((isIntegral!(CommonType!(B, E)) || isPointer!(CommonType!(B, E))) |
| && isIntegral!S) |
| { |
| import std.conv : unsigned; |
| |
| alias Value = CommonType!(Unqual!B, Unqual!E); |
| alias StepType = Unqual!S; |
| |
| assert(step != 0 || begin == end); |
| |
| static struct Result |
| { |
| private Value current, last; |
| private StepType step; // by convention, 0 if range is empty |
| |
| this(Value current, Value pastLast, StepType step) |
| { |
| if (current < pastLast && step > 0) |
| { |
| // Iterating upward |
| assert(unsigned((pastLast - current) / step) <= size_t.max); |
| // Cast below can't fail because current < pastLast |
| this.last = cast(Value) (pastLast - 1); |
| this.last -= unsigned(this.last - current) % step; |
| } |
| else if (current > pastLast && step < 0) |
| { |
| // Iterating downward |
| assert(unsigned((current - pastLast) / (0 - step)) <= size_t.max); |
| // Cast below can't fail because current > pastLast |
| this.last = cast(Value) (pastLast + 1); |
| this.last += unsigned(current - this.last) % (0 - step); |
| } |
| else |
| { |
| // Initialize an empty range |
| this.step = 0; |
| return; |
| } |
| this.step = step; |
| this.current = current; |
| } |
| |
| @property bool empty() const { return step == 0; } |
| @property inout(Value) front() inout { assert(!empty); return current; } |
| void popFront() |
| { |
| assert(!empty); |
| if (current == last) step = 0; |
| else current += step; |
| } |
| |
| @property inout(Value) back() inout |
| { |
| assert(!empty); |
| return last; |
| } |
| void popBack() |
| { |
| assert(!empty); |
| if (current == last) step = 0; |
| else last -= step; |
| } |
| |
| @property auto save() { return this; } |
| |
| inout(Value) opIndex(ulong n) inout |
| { |
| assert(n < this.length); |
| |
| // Just cast to Value here because doing so gives overflow behavior |
| // consistent with calling popFront() n times. |
| return cast(inout Value) (current + step * n); |
| } |
| auto opBinaryRight(string op)(Value val) const |
| if (op == "in") |
| { |
| if (empty) return false; |
| //cast to avoid becoming unsigned |
| auto supposedIndex = cast(StepType)(val - current) / step; |
| return supposedIndex < length && supposedIndex * step + current == val; |
| } |
| auto contains(Value x){return x in this;} |
| inout(Result) opSlice() inout { return this; } |
| inout(Result) opSlice(ulong lower, ulong upper) inout |
| { |
| assert(upper >= lower && upper <= this.length); |
| |
| return cast(inout Result) Result( |
| cast(Value)(current + lower * step), |
| cast(Value)(current + upper * step), |
| step); |
| } |
| @property size_t length() const |
| { |
| if (step > 0) |
| return 1 + cast(size_t) (unsigned(last - current) / step); |
| if (step < 0) |
| return 1 + cast(size_t) (unsigned(current - last) / (0 - step)); |
| return 0; |
| } |
| |
| alias opDollar = length; |
| } |
| |
| return Result(begin, end, step); |
| } |
| |
| /// Ditto |
| auto iota(B, E)(B begin, E end) |
| if (isFloatingPoint!(CommonType!(B, E))) |
| { |
| return iota(begin, end, CommonType!(B, E)(1)); |
| } |
| |
| /// Ditto |
| auto iota(B, E)(B begin, E end) |
| if (isIntegral!(CommonType!(B, E)) || isPointer!(CommonType!(B, E))) |
| { |
| import std.conv : unsigned; |
| |
| alias Value = CommonType!(Unqual!B, Unqual!E); |
| |
| static struct Result |
| { |
| private Value current, pastLast; |
| |
| this(Value current, Value pastLast) |
| { |
| if (current < pastLast) |
| { |
| assert(unsigned(pastLast - current) <= size_t.max); |
| |
| this.current = current; |
| this.pastLast = pastLast; |
| } |
| else |
| { |
| // Initialize an empty range |
| this.current = this.pastLast = current; |
| } |
| } |
| |
| @property bool empty() const { return current == pastLast; } |
| @property inout(Value) front() inout { assert(!empty); return current; } |
| void popFront() { assert(!empty); ++current; } |
| |
| @property inout(Value) back() inout { assert(!empty); return cast(inout(Value))(pastLast - 1); } |
| void popBack() { assert(!empty); --pastLast; } |
| |
| @property auto save() { return this; } |
| |
| inout(Value) opIndex(size_t n) inout |
| { |
| assert(n < this.length); |
| |
| // Just cast to Value here because doing so gives overflow behavior |
| // consistent with calling popFront() n times. |
| return cast(inout Value) (current + n); |
| } |
| auto opBinaryRight(string op)(Value val) const |
| if (op == "in") |
| { |
| return current <= val && val < pastLast; |
| } |
| auto contains(Value x){return x in this;} |
| inout(Result) opSlice() inout { return this; } |
| inout(Result) opSlice(ulong lower, ulong upper) inout |
| { |
| assert(upper >= lower && upper <= this.length); |
| |
| return cast(inout Result) Result(cast(Value)(current + lower), |
| cast(Value)(pastLast - (length - upper))); |
| } |
| @property size_t length() const |
| { |
| return cast(size_t)(pastLast - current); |
| } |
| |
| alias opDollar = length; |
| } |
| |
| return Result(begin, end); |
| } |
| |
| /// Ditto |
| auto iota(E)(E end) |
| if (is(typeof(iota(E(0), end)))) |
| { |
| E begin = E(0); |
| return iota(begin, end); |
| } |
| |
| /// Ditto |
| // Specialization for floating-point types |
| auto iota(B, E, S)(B begin, E end, S step) |
| if (isFloatingPoint!(CommonType!(B, E, S))) |
| in |
| { |
| assert(step != 0, "iota: step must not be 0"); |
| assert((end - begin) / step >= 0, "iota: incorrect startup parameters"); |
| } |
| body |
| { |
| alias Value = Unqual!(CommonType!(B, E, S)); |
| static struct Result |
| { |
| private Value start, step; |
| private size_t index, count; |
| |
| this(Value start, Value end, Value step) |
| { |
| import std.conv : to; |
| |
| this.start = start; |
| this.step = step; |
| immutable fcount = (end - start) / step; |
| count = to!size_t(fcount); |
| auto pastEnd = start + count * step; |
| if (step > 0) |
| { |
| if (pastEnd < end) ++count; |
| assert(start + count * step >= end); |
| } |
| else |
| { |
| if (pastEnd > end) ++count; |
| assert(start + count * step <= end); |
| } |
| } |
| |
| @property bool empty() const { return index == count; } |
| @property Value front() const { assert(!empty); return start + step * index; } |
| void popFront() |
| { |
| assert(!empty); |
| ++index; |
| } |
| @property Value back() const |
| { |
| assert(!empty); |
| return start + step * (count - 1); |
| } |
| void popBack() |
| { |
| assert(!empty); |
| --count; |
| } |
| |
| @property auto save() { return this; } |
| |
| Value opIndex(size_t n) const |
| { |
| assert(n < count); |
| return start + step * (n + index); |
| } |
| inout(Result) opSlice() inout |
| { |
| return this; |
| } |
| inout(Result) opSlice(size_t lower, size_t upper) inout |
| { |
| assert(upper >= lower && upper <= count); |
| |
| Result ret = this; |
| ret.index += lower; |
| ret.count = upper - lower + ret.index; |
| return cast(inout Result) ret; |
| } |
| @property size_t length() const |
| { |
| return count - index; |
| } |
| |
| alias opDollar = length; |
| } |
| |
| return Result(begin, end, step); |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.math : approxEqual; |
| |
| auto r = iota(0, 10, 1); |
| assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); |
| assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); |
| assert(3 in r); |
| assert(r.contains(3)); //Same as above |
| assert(!(10 in r)); |
| assert(!(-8 in r)); |
| r = iota(0, 11, 3); |
| assert(equal(r, [0, 3, 6, 9])); |
| assert(r[2] == 6); |
| assert(!(2 in r)); |
| auto rf = iota(0.0, 0.5, 0.1); |
| assert(approxEqual(rf, [0.0, 0.1, 0.2, 0.3, 0.4])); |
| } |
| |
| nothrow @nogc @safe unittest |
| { |
| //float overloads use std.conv.to so can't be @nogc or nothrow |
| alias ssize_t = Signed!size_t; |
| assert(iota(ssize_t.max, 0, -1).length == ssize_t.max); |
| assert(iota(ssize_t.max, ssize_t.min, -1).length == size_t.max); |
| assert(iota(ssize_t.max, ssize_t.min, -2).length == 1 + size_t.max / 2); |
| assert(iota(ssize_t.min, ssize_t.max, 2).length == 1 + size_t.max / 2); |
| assert(iota(ssize_t.max, ssize_t.min, -3).length == size_t.max / 3); |
| } |
| |
| debug @system unittest |
| {//check the contracts |
| import core.exception : AssertError; |
| import std.exception : assertThrown; |
| assertThrown!AssertError(iota(1,2,0)); |
| assertThrown!AssertError(iota(0f,1f,0f)); |
| assertThrown!AssertError(iota(1f,0f,0.1f)); |
| assertThrown!AssertError(iota(0f,1f,-0.1f)); |
| } |
| |
| @system unittest |
| { |
| int[] a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; |
| auto r1 = iota(a.ptr, a.ptr + a.length, 1); |
| assert(r1.front == a.ptr); |
| assert(r1.back == a.ptr + a.length - 1); |
| assert(&a[4] in r1); |
| } |
| |
| @safe unittest |
| { |
| assert(iota(1UL, 0UL).length == 0); |
| assert(iota(1UL, 0UL, 1).length == 0); |
| assert(iota(0, 1, 1).length == 1); |
| assert(iota(1, 0, -1).length == 1); |
| assert(iota(0, 1, -1).length == 0); |
| assert(iota(ulong.max, 0).length == 0); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.searching : count; |
| import std.math : approxEqual, nextUp, nextDown; |
| import std.meta : AliasSeq; |
| |
| static assert(is(ElementType!(typeof(iota(0f))) == float)); |
| |
| static assert(hasLength!(typeof(iota(0, 2)))); |
| auto r = iota(0, 10, 1); |
| assert(r[$ - 1] == 9); |
| assert(equal(r, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9][])); |
| |
| auto rSlice = r[2 .. 8]; |
| assert(equal(rSlice, [2, 3, 4, 5, 6, 7])); |
| |
| rSlice.popFront(); |
| assert(rSlice[0] == rSlice.front); |
| assert(rSlice.front == 3); |
| |
| rSlice.popBack(); |
| assert(rSlice[rSlice.length - 1] == rSlice.back); |
| assert(rSlice.back == 6); |
| |
| rSlice = r[0 .. 4]; |
| assert(equal(rSlice, [0, 1, 2, 3])); |
| assert(3 in rSlice); |
| assert(!(4 in rSlice)); |
| |
| auto rr = iota(10); |
| assert(equal(rr, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9][])); |
| |
| r = iota(0, -10, -1); |
| assert(equal(r, [0, -1, -2, -3, -4, -5, -6, -7, -8, -9][])); |
| rSlice = r[3 .. 9]; |
| assert(equal(rSlice, [-3, -4, -5, -6, -7, -8])); |
| |
| r = iota(0, -6, -3); |
| assert(equal(r, [0, -3][])); |
| rSlice = r[1 .. 2]; |
| assert(equal(rSlice, [-3])); |
| |
| r = iota(0, -7, -3); |
| assert(equal(r, [0, -3, -6][])); |
| assert(0 in r); |
| assert(-6 in r); |
| rSlice = r[1 .. 3]; |
| assert(equal(rSlice, [-3, -6])); |
| assert(!(0 in rSlice)); |
| assert(!(-2 in rSlice)); |
| assert(!(-5 in rSlice)); |
| assert(!(3 in rSlice)); |
| assert(!(-9 in rSlice)); |
| |
| r = iota(0, 11, 3); |
| assert(equal(r, [0, 3, 6, 9][])); |
| assert(r[2] == 6); |
| rSlice = r[1 .. 3]; |
| assert(equal(rSlice, [3, 6])); |
| |
| auto rf = iota(0.0, 0.5, 0.1); |
| assert(approxEqual(rf, [0.0, 0.1, 0.2, 0.3, 0.4][])); |
| assert(rf.length == 5); |
| |
| rf.popFront(); |
| assert(rf.length == 4); |
| |
| auto rfSlice = rf[1 .. 4]; |
| assert(rfSlice.length == 3); |
| assert(approxEqual(rfSlice, [0.2, 0.3, 0.4])); |
| |
| rfSlice.popFront(); |
| assert(approxEqual(rfSlice[0], 0.3)); |
| |
| rf.popFront(); |
| assert(rf.length == 3); |
| |
| rfSlice = rf[1 .. 3]; |
| assert(rfSlice.length == 2); |
| assert(approxEqual(rfSlice, [0.3, 0.4])); |
| assert(approxEqual(rfSlice[0], 0.3)); |
| |
| // With something just above 0.5 |
| rf = iota(0.0, nextUp(0.5), 0.1); |
| assert(approxEqual(rf, [0.0, 0.1, 0.2, 0.3, 0.4, 0.5][])); |
| rf.popBack(); |
| assert(rf[rf.length - 1] == rf.back); |
| assert(approxEqual(rf.back, 0.4)); |
| assert(rf.length == 5); |
| |
| // going down |
| rf = iota(0.0, -0.5, -0.1); |
| assert(approxEqual(rf, [0.0, -0.1, -0.2, -0.3, -0.4][])); |
| rfSlice = rf[2 .. 5]; |
| assert(approxEqual(rfSlice, [-0.2, -0.3, -0.4])); |
| |
| rf = iota(0.0, nextDown(-0.5), -0.1); |
| assert(approxEqual(rf, [0.0, -0.1, -0.2, -0.3, -0.4, -0.5][])); |
| |
| // iota of longs |
| auto rl = iota(5_000_000L); |
| assert(rl.length == 5_000_000L); |
| assert(0 in rl); |
| assert(4_000_000L in rl); |
| assert(!(-4_000_000L in rl)); |
| assert(!(5_000_000L in rl)); |
| |
| // iota of longs with steps |
| auto iota_of_longs_with_steps = iota(50L, 101L, 10); |
| assert(iota_of_longs_with_steps.length == 6); |
| assert(equal(iota_of_longs_with_steps, [50L, 60L, 70L, 80L, 90L, 100L])); |
| |
| // iota of unsigned zero length (issue 6222, actually trying to consume it |
| // is the only way to find something is wrong because the public |
| // properties are all correct) |
| auto iota_zero_unsigned = iota(0, 0u, 3); |
| assert(count(iota_zero_unsigned) == 0); |
| |
| // unsigned reverse iota can be buggy if .length doesn't take them into |
| // account (issue 7982). |
| assert(iota(10u, 0u, -1).length == 10); |
| assert(iota(10u, 0u, -2).length == 5); |
| assert(iota(uint.max, uint.max-10, -1).length == 10); |
| assert(iota(uint.max, uint.max-10, -2).length == 5); |
| assert(iota(uint.max, 0u, -1).length == uint.max); |
| |
| assert(20 in iota(20u, 10u, -2)); |
| assert(16 in iota(20u, 10u, -2)); |
| assert(!(15 in iota(20u, 10u, -2))); |
| assert(!(10 in iota(20u, 10u, -2))); |
| assert(!(uint.max in iota(20u, 10u, -1))); |
| assert(!(int.min in iota(20u, 10u, -1))); |
| assert(!(int.max in iota(20u, 10u, -1))); |
| |
| |
| // Issue 8920 |
| foreach (Type; AliasSeq!(byte, ubyte, short, ushort, |
| int, uint, long, ulong)) |
| { |
| Type val; |
| foreach (i; iota(cast(Type) 0, cast(Type) 10)) { val++; } |
| assert(val == 10); |
| } |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.mutation : copy; |
| auto idx = new size_t[100]; |
| copy(iota(0, idx.length), idx); |
| } |
| |
| @safe unittest |
| { |
| import std.meta : AliasSeq; |
| foreach (range; AliasSeq!(iota(2, 27, 4), |
| iota(3, 9), |
| iota(2.7, 12.3, .1), |
| iota(3.2, 9.7))) |
| { |
| const cRange = range; |
| const e = cRange.empty; |
| const f = cRange.front; |
| const b = cRange.back; |
| const i = cRange[2]; |
| const s1 = cRange[]; |
| const s2 = cRange[0 .. 3]; |
| const l = cRange.length; |
| } |
| } |
| |
| @system unittest |
| { |
| //The ptr stuff can't be done at compile time, so we unfortunately end |
| //up with some code duplication here. |
| auto arr = [0, 5, 3, 5, 5, 7, 9, 2, 0, 42, 7, 6]; |
| |
| { |
| const cRange = iota(arr.ptr, arr.ptr + arr.length, 3); |
| const e = cRange.empty; |
| const f = cRange.front; |
| const b = cRange.back; |
| const i = cRange[2]; |
| const s1 = cRange[]; |
| const s2 = cRange[0 .. 3]; |
| const l = cRange.length; |
| } |
| |
| { |
| const cRange = iota(arr.ptr, arr.ptr + arr.length); |
| const e = cRange.empty; |
| const f = cRange.front; |
| const b = cRange.back; |
| const i = cRange[2]; |
| const s1 = cRange[]; |
| const s2 = cRange[0 .. 3]; |
| const l = cRange.length; |
| } |
| } |
| |
| @nogc nothrow pure @safe |
| unittest |
| { |
| { |
| ushort start = 0, end = 10, step = 2; |
| foreach (i; iota(start, end, step)) |
| static assert(is(typeof(i) == ushort)); |
| } |
| { |
| ubyte start = 0, end = 255, step = 128; |
| uint x; |
| foreach (i; iota(start, end, step)) |
| { |
| static assert(is(typeof(i) == ubyte)); |
| ++x; |
| } |
| assert(x == 2); |
| } |
| } |
| |
| /* Generic overload that handles arbitrary types that support arithmetic |
| * operations. |
| * |
| * User-defined types such as $(REF BigInt, std,bigint) are also supported, as long |
| * as they can be incremented with $(D ++) and compared with $(D <) or $(D ==). |
| */ |
| /// ditto |
| auto iota(B, E)(B begin, E end) |
| if (!isIntegral!(CommonType!(B, E)) && |
| !isFloatingPoint!(CommonType!(B, E)) && |
| !isPointer!(CommonType!(B, E)) && |
| is(typeof((ref B b) { ++b; })) && |
| (is(typeof(B.init < E.init)) || is(typeof(B.init == E.init))) ) |
| { |
| static struct Result |
| { |
| B current; |
| E end; |
| |
| @property bool empty() |
| { |
| static if (is(typeof(B.init < E.init))) |
| return !(current < end); |
| else static if (is(typeof(B.init != E.init))) |
| return current == end; |
| else |
| static assert(0); |
| } |
| @property auto front() { return current; } |
| void popFront() |
| { |
| assert(!empty); |
| ++current; |
| } |
| } |
| return Result(begin, end); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| // Test iota() for a type that only supports ++ and != but does not have |
| // '<'-ordering. |
| struct Cyclic(int wrapAround) |
| { |
| int current; |
| |
| this(int start) { current = start % wrapAround; } |
| |
| bool opEquals(Cyclic c) const { return current == c.current; } |
| bool opEquals(int i) const { return current == i; } |
| void opUnary(string op)() if (op == "++") |
| { |
| current = (current + 1) % wrapAround; |
| } |
| } |
| alias Cycle5 = Cyclic!5; |
| |
| // Easy case |
| auto i1 = iota(Cycle5(1), Cycle5(4)); |
| assert(i1.equal([1, 2, 3])); |
| |
| // Wraparound case |
| auto i2 = iota(Cycle5(3), Cycle5(2)); |
| assert(i2.equal([3, 4, 0, 1 ])); |
| } |
| |
| /** |
| Options for the $(LREF FrontTransversal) and $(LREF Transversal) ranges |
| (below). |
| */ |
| enum TransverseOptions |
| { |
| /** |
| When transversed, the elements of a range of ranges are assumed to |
| have different lengths (e.g. a jagged array). |
| */ |
| assumeJagged, //default |
| /** |
| The transversal enforces that the elements of a range of ranges have |
| all the same length (e.g. an array of arrays, all having the same |
| length). Checking is done once upon construction of the transversal |
| range. |
| */ |
| enforceNotJagged, |
| /** |
| The transversal assumes, without verifying, that the elements of a |
| range of ranges have all the same length. This option is useful if |
| checking was already done from the outside of the range. |
| */ |
| assumeNotJagged, |
| } |
| |
| /** |
| Given a range of ranges, iterate transversally through the first |
| elements of each of the enclosed ranges. |
| */ |
| struct FrontTransversal(Ror, |
| TransverseOptions opt = TransverseOptions.assumeJagged) |
| { |
| alias RangeOfRanges = Unqual!(Ror); |
| alias RangeType = .ElementType!RangeOfRanges; |
| alias ElementType = .ElementType!RangeType; |
| |
| private void prime() |
| { |
| static if (opt == TransverseOptions.assumeJagged) |
| { |
| while (!_input.empty && _input.front.empty) |
| { |
| _input.popFront(); |
| } |
| static if (isBidirectionalRange!RangeOfRanges) |
| { |
| while (!_input.empty && _input.back.empty) |
| { |
| _input.popBack(); |
| } |
| } |
| } |
| } |
| |
| /** |
| Construction from an input. |
| */ |
| this(RangeOfRanges input) |
| { |
| _input = input; |
| prime(); |
| static if (opt == TransverseOptions.enforceNotJagged) |
| // (isRandomAccessRange!RangeOfRanges |
| // && hasLength!RangeType) |
| { |
| import std.exception : enforce; |
| |
| if (empty) return; |
| immutable commonLength = _input.front.length; |
| foreach (e; _input) |
| { |
| enforce(e.length == commonLength); |
| } |
| } |
| } |
| |
| /** |
| Forward range primitives. |
| */ |
| static if (isInfinite!RangeOfRanges) |
| { |
| enum bool empty = false; |
| } |
| else |
| { |
| @property bool empty() |
| { |
| static if (opt != TransverseOptions.assumeJagged) |
| { |
| if (!_input.empty) |
| return _input.front.empty; |
| } |
| |
| return _input.empty; |
| } |
| } |
| |
| /// Ditto |
| @property auto ref front() |
| { |
| assert(!empty, "Attempting to fetch the front of an empty FrontTransversal"); |
| return _input.front.front; |
| } |
| |
| /// Ditto |
| static if (hasMobileElements!RangeType) |
| { |
| ElementType moveFront() |
| { |
| return _input.front.moveFront(); |
| } |
| } |
| |
| static if (hasAssignableElements!RangeType) |
| { |
| @property void front(ElementType val) |
| { |
| _input.front.front = val; |
| } |
| } |
| |
| /// Ditto |
| void popFront() |
| { |
| assert(!empty, "Attempting to popFront an empty FrontTransversal"); |
| _input.popFront(); |
| prime(); |
| } |
| |
| /** |
| Duplicates this $(D frontTransversal). Note that only the encapsulating |
| range of range will be duplicated. Underlying ranges will not be |
| duplicated. |
| */ |
| static if (isForwardRange!RangeOfRanges) |
| { |
| @property FrontTransversal save() |
| { |
| return FrontTransversal(_input.save); |
| } |
| } |
| |
| static if (isBidirectionalRange!RangeOfRanges) |
| { |
| /** |
| Bidirectional primitives. They are offered if $(D |
| isBidirectionalRange!RangeOfRanges). |
| */ |
| @property auto ref back() |
| { |
| assert(!empty, "Attempting to fetch the back of an empty FrontTransversal"); |
| return _input.back.front; |
| } |
| /// Ditto |
| void popBack() |
| { |
| assert(!empty, "Attempting to popBack an empty FrontTransversal"); |
| _input.popBack(); |
| prime(); |
| } |
| |
| /// Ditto |
| static if (hasMobileElements!RangeType) |
| { |
| ElementType moveBack() |
| { |
| return _input.back.moveFront(); |
| } |
| } |
| |
| static if (hasAssignableElements!RangeType) |
| { |
| @property void back(ElementType val) |
| { |
| _input.back.front = val; |
| } |
| } |
| } |
| |
| static if (isRandomAccessRange!RangeOfRanges && |
| (opt == TransverseOptions.assumeNotJagged || |
| opt == TransverseOptions.enforceNotJagged)) |
| { |
| /** |
| Random-access primitive. It is offered if $(D |
| isRandomAccessRange!RangeOfRanges && (opt == |
| TransverseOptions.assumeNotJagged || opt == |
| TransverseOptions.enforceNotJagged)). |
| */ |
| auto ref opIndex(size_t n) |
| { |
| return _input[n].front; |
| } |
| |
| /// Ditto |
| static if (hasMobileElements!RangeType) |
| { |
| ElementType moveAt(size_t n) |
| { |
| return _input[n].moveFront(); |
| } |
| } |
| /// Ditto |
| static if (hasAssignableElements!RangeType) |
| { |
| void opIndexAssign(ElementType val, size_t n) |
| { |
| _input[n].front = val; |
| } |
| } |
| /// Ditto |
| static if (hasLength!RangeOfRanges) |
| { |
| @property size_t length() |
| { |
| return _input.length; |
| } |
| |
| alias opDollar = length; |
| } |
| |
| /** |
| Slicing if offered if $(D RangeOfRanges) supports slicing and all the |
| conditions for supporting indexing are met. |
| */ |
| static if (hasSlicing!RangeOfRanges) |
| { |
| typeof(this) opSlice(size_t lower, size_t upper) |
| { |
| return typeof(this)(_input[lower .. upper]); |
| } |
| } |
| } |
| |
| auto opSlice() { return this; } |
| |
| private: |
| RangeOfRanges _input; |
| } |
| |
| /// Ditto |
| FrontTransversal!(RangeOfRanges, opt) frontTransversal( |
| TransverseOptions opt = TransverseOptions.assumeJagged, |
| RangeOfRanges) |
| (RangeOfRanges rr) |
| { |
| return typeof(return)(rr); |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| int[][] x = new int[][2]; |
| x[0] = [1, 2]; |
| x[1] = [3, 4]; |
| auto ror = frontTransversal(x); |
| assert(equal(ror, [ 1, 3 ][])); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.internal.test.dummyrange : AllDummyRanges, DummyRange, ReturnBy; |
| |
| static assert(is(FrontTransversal!(immutable int[][]))); |
| |
| foreach (DummyType; AllDummyRanges) |
| { |
| auto dummies = |
| [DummyType.init, DummyType.init, DummyType.init, DummyType.init]; |
| |
| foreach (i, ref elem; dummies) |
| { |
| // Just violate the DummyRange abstraction to get what I want. |
| elem.arr = elem.arr[i..$ - (3 - i)]; |
| } |
| |
| auto ft = frontTransversal!(TransverseOptions.assumeNotJagged)(dummies); |
| static if (isForwardRange!DummyType) |
| { |
| static assert(isForwardRange!(typeof(ft))); |
| } |
| |
| assert(equal(ft, [1, 2, 3, 4])); |
| |
| // Test slicing. |
| assert(equal(ft[0 .. 2], [1, 2])); |
| assert(equal(ft[1 .. 3], [2, 3])); |
| |
| assert(ft.front == ft.moveFront()); |
| assert(ft.back == ft.moveBack()); |
| assert(ft.moveAt(1) == ft[1]); |
| |
| |
| // Test infiniteness propagation. |
| static assert(isInfinite!(typeof(frontTransversal(repeat("foo"))))); |
| |
| static if (DummyType.r == ReturnBy.Reference) |
| { |
| { |
| ft.front++; |
| scope(exit) ft.front--; |
| assert(dummies.front.front == 2); |
| } |
| |
| { |
| ft.front = 5; |
| scope(exit) ft.front = 1; |
| assert(dummies[0].front == 5); |
| } |
| |
| { |
| ft.back = 88; |
| scope(exit) ft.back = 4; |
| assert(dummies.back.front == 88); |
| } |
| |
| { |
| ft[1] = 99; |
| scope(exit) ft[1] = 2; |
| assert(dummies[1].front == 99); |
| } |
| } |
| } |
| } |
| |
| // Issue 16363 |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| int[][] darr = [[0, 1], [4, 5]]; |
| auto ft = frontTransversal!(TransverseOptions.assumeNotJagged)(darr); |
| |
| assert(equal(ft, [0, 4])); |
| static assert(isRandomAccessRange!(typeof(ft))); |
| } |
| |
| // Bugzilla 16442 |
| @safe unittest |
| { |
| int[][] arr = [[], []]; |
| |
| auto ft1 = frontTransversal!(TransverseOptions.assumeNotJagged)(arr); |
| assert(ft1.empty); |
| |
| auto ft2 = frontTransversal!(TransverseOptions.enforceNotJagged)(arr); |
| assert(ft2.empty); |
| } |
| |
| /** |
| Given a range of ranges, iterate transversally through the |
| `n`th element of each of the enclosed ranges. |
| |
| Params: |
| opt = Controls the assumptions the function makes about the lengths |
| of the ranges |
| rr = An input range of random access ranges |
| Returns: |
| At minimum, an input range. Range primitives such as bidirectionality |
| and random access are given if the element type of `rr` provides them. |
| */ |
| struct Transversal(Ror, |
| TransverseOptions opt = TransverseOptions.assumeJagged) |
| { |
| private alias RangeOfRanges = Unqual!Ror; |
| private alias InnerRange = ElementType!RangeOfRanges; |
| private alias E = ElementType!InnerRange; |
| |
| private void prime() |
| { |
| static if (opt == TransverseOptions.assumeJagged) |
| { |
| while (!_input.empty && _input.front.length <= _n) |
| { |
| _input.popFront(); |
| } |
| static if (isBidirectionalRange!RangeOfRanges) |
| { |
| while (!_input.empty && _input.back.length <= _n) |
| { |
| _input.popBack(); |
| } |
| } |
| } |
| } |
| |
| /** |
| Construction from an input and an index. |
| */ |
| this(RangeOfRanges input, size_t n) |
| { |
| _input = input; |
| _n = n; |
| prime(); |
| static if (opt == TransverseOptions.enforceNotJagged) |
| { |
| import std.exception : enforce; |
| |
| if (empty) return; |
| immutable commonLength = _input.front.length; |
| foreach (e; _input) |
| { |
| enforce(e.length == commonLength); |
| } |
| } |
| } |
| |
| /** |
| Forward range primitives. |
| */ |
| static if (isInfinite!(RangeOfRanges)) |
| { |
| enum bool empty = false; |
| } |
| else |
| { |
| @property bool empty() |
| { |
| return _input.empty; |
| } |
| } |
| |
| /// Ditto |
| @property auto ref front() |
| { |
| assert(!empty, "Attempting to fetch the front of an empty Transversal"); |
| return _input.front[_n]; |
| } |
| |
| /// Ditto |
| static if (hasMobileElements!InnerRange) |
| { |
| E moveFront() |
| { |
| return _input.front.moveAt(_n); |
| } |
| } |
| |
| /// Ditto |
| static if (hasAssignableElements!InnerRange) |
| { |
| @property void front(E val) |
| { |
| _input.front[_n] = val; |
| } |
| } |
| |
| |
| /// Ditto |
| void popFront() |
| { |
| assert(!empty, "Attempting to popFront an empty Transversal"); |
| _input.popFront(); |
| prime(); |
| } |
| |
| /// Ditto |
| static if (isForwardRange!RangeOfRanges) |
| { |
| @property typeof(this) save() |
| { |
| auto ret = this; |
| ret._input = _input.save; |
| return ret; |
| } |
| } |
| |
| static if (isBidirectionalRange!RangeOfRanges) |
| { |
| /** |
| Bidirectional primitives. They are offered if $(D |
| isBidirectionalRange!RangeOfRanges). |
| */ |
| @property auto ref back() |
| { |
| assert(!empty, "Attempting to fetch the back of an empty Transversal"); |
| return _input.back[_n]; |
| } |
| |
| /// Ditto |
| void popBack() |
| { |
| assert(!empty, "Attempting to popBack an empty Transversal"); |
| _input.popBack(); |
| prime(); |
| } |
| |
| /// Ditto |
| static if (hasMobileElements!InnerRange) |
| { |
| E moveBack() |
| { |
| return _input.back.moveAt(_n); |
| } |
| } |
| |
| /// Ditto |
| static if (hasAssignableElements!InnerRange) |
| { |
| @property void back(E val) |
| { |
| _input.back[_n] = val; |
| } |
| } |
| |
| } |
| |
| static if (isRandomAccessRange!RangeOfRanges && |
| (opt == TransverseOptions.assumeNotJagged || |
| opt == TransverseOptions.enforceNotJagged)) |
| { |
| /** |
| Random-access primitive. It is offered if $(D |
| isRandomAccessRange!RangeOfRanges && (opt == |
| TransverseOptions.assumeNotJagged || opt == |
| TransverseOptions.enforceNotJagged)). |
| */ |
| auto ref opIndex(size_t n) |
| { |
| return _input[n][_n]; |
| } |
| |
| /// Ditto |
| static if (hasMobileElements!InnerRange) |
| { |
| E moveAt(size_t n) |
| { |
| return _input[n].moveAt(_n); |
| } |
| } |
| |
| /// Ditto |
| static if (hasAssignableElements!InnerRange) |
| { |
| void opIndexAssign(E val, size_t n) |
| { |
| _input[n][_n] = val; |
| } |
| } |
| |
| /// Ditto |
| static if (hasLength!RangeOfRanges) |
| { |
| @property size_t length() |
| { |
| return _input.length; |
| } |
| |
| alias opDollar = length; |
| } |
| |
| /** |
| Slicing if offered if $(D RangeOfRanges) supports slicing and all the |
| conditions for supporting indexing are met. |
| */ |
| static if (hasSlicing!RangeOfRanges) |
| { |
| typeof(this) opSlice(size_t lower, size_t upper) |
| { |
| return typeof(this)(_input[lower .. upper], _n); |
| } |
| } |
| } |
| |
| auto opSlice() { return this; } |
| |
| private: |
| RangeOfRanges _input; |
| size_t _n; |
| } |
| |
| /// Ditto |
| Transversal!(RangeOfRanges, opt) transversal |
| (TransverseOptions opt = TransverseOptions.assumeJagged, RangeOfRanges) |
| (RangeOfRanges rr, size_t n) |
| { |
| return typeof(return)(rr, n); |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| int[][] x = new int[][2]; |
| x[0] = [1, 2]; |
| x[1] = [3, 4]; |
| auto ror = transversal(x, 1); |
| assert(equal(ror, [ 2, 4 ][])); |
| } |
| |
| @safe unittest |
| { |
| import std.internal.test.dummyrange : DummyRange, Length, RangeType, ReturnBy; |
| |
| int[][] x = new int[][2]; |
| x[0] = [ 1, 2 ]; |
| x[1] = [3, 4]; |
| auto ror = transversal!(TransverseOptions.assumeNotJagged)(x, 1); |
| auto witness = [ 2, 4 ]; |
| uint i; |
| foreach (e; ror) assert(e == witness[i++]); |
| assert(i == 2); |
| assert(ror.length == 2); |
| |
| static assert(is(Transversal!(immutable int[][]))); |
| |
| // Make sure ref, assign is being propagated. |
| { |
| ror.front++; |
| scope(exit) ror.front--; |
| assert(x[0][1] == 3); |
| } |
| { |
| ror.front = 5; |
| scope(exit) ror.front = 2; |
| assert(x[0][1] == 5); |
| assert(ror.moveFront() == 5); |
| } |
| { |
| ror.back = 999; |
| scope(exit) ror.back = 4; |
| assert(x[1][1] == 999); |
| assert(ror.moveBack() == 999); |
| } |
| { |
| ror[0] = 999; |
| scope(exit) ror[0] = 2; |
| assert(x[0][1] == 999); |
| assert(ror.moveAt(0) == 999); |
| } |
| |
| // Test w/o ref return. |
| alias D = DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random); |
| auto drs = [D.init, D.init]; |
| foreach (num; 0 .. 10) |
| { |
| auto t = transversal!(TransverseOptions.enforceNotJagged)(drs, num); |
| assert(t[0] == t[1]); |
| assert(t[1] == num + 1); |
| } |
| |
| static assert(isInfinite!(typeof(transversal(repeat([1,2,3]), 1)))); |
| |
| // Test slicing. |
| auto mat = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]]; |
| auto mat1 = transversal!(TransverseOptions.assumeNotJagged)(mat, 1)[1 .. 3]; |
| assert(mat1[0] == 6); |
| assert(mat1[1] == 10); |
| } |
| |
| struct Transposed(RangeOfRanges) |
| if (isForwardRange!RangeOfRanges && |
| isInputRange!(ElementType!RangeOfRanges) && |
| hasAssignableElements!RangeOfRanges) |
| { |
| //alias ElementType = typeof(map!"a.front"(RangeOfRanges.init)); |
| |
| this(RangeOfRanges input) |
| { |
| this._input = input; |
| } |
| |
| @property auto front() |
| { |
| import std.algorithm.iteration : filter, map; |
| return _input.save |
| .filter!(a => !a.empty) |
| .map!(a => a.front); |
| } |
| |
| void popFront() |
| { |
| // Advance the position of each subrange. |
| auto r = _input.save; |
| while (!r.empty) |
| { |
| auto e = r.front; |
| if (!e.empty) |
| { |
| e.popFront(); |
| r.front = e; |
| } |
| |
| r.popFront(); |
| } |
| } |
| |
| // ElementType opIndex(size_t n) |
| // { |
| // return _input[n].front; |
| // } |
| |
| @property bool empty() |
| { |
| if (_input.empty) return true; |
| foreach (e; _input.save) |
| { |
| if (!e.empty) return false; |
| } |
| return true; |
| } |
| |
| @property Transposed save() |
| { |
| return Transposed(_input.save); |
| } |
| |
| auto opSlice() { return this; } |
| |
| private: |
| RangeOfRanges _input; |
| } |
| |
| @safe unittest |
| { |
| // Boundary case: transpose of empty range should be empty |
| int[][] ror = []; |
| assert(transposed(ror).empty); |
| } |
| |
| // Issue 9507 |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| auto r = [[1,2], [3], [4,5], [], [6]]; |
| assert(r.transposed.equal!equal([ |
| [1, 3, 4, 6], |
| [2, 5] |
| ])); |
| } |
| |
| /** |
| Given a range of ranges, returns a range of ranges where the $(I i)'th subrange |
| contains the $(I i)'th elements of the original subranges. |
| */ |
| Transposed!RangeOfRanges transposed(RangeOfRanges)(RangeOfRanges rr) |
| if (isForwardRange!RangeOfRanges && |
| isInputRange!(ElementType!RangeOfRanges) && |
| hasAssignableElements!RangeOfRanges) |
| { |
| return Transposed!RangeOfRanges(rr); |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| int[][] ror = [ |
| [1, 2, 3], |
| [4, 5, 6] |
| ]; |
| auto xp = transposed(ror); |
| assert(equal!"a.equal(b)"(xp, [ |
| [1, 4], |
| [2, 5], |
| [3, 6] |
| ])); |
| } |
| |
| /// |
| @safe unittest |
| { |
| int[][] x = new int[][2]; |
| x[0] = [1, 2]; |
| x[1] = [3, 4]; |
| auto tr = transposed(x); |
| int[][] witness = [ [ 1, 3 ], [ 2, 4 ] ]; |
| uint i; |
| |
| foreach (e; tr) |
| { |
| assert(array(e) == witness[i++]); |
| } |
| } |
| |
| // Issue 8764 |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| ulong[1] t0 = [ 123 ]; |
| |
| assert(!hasAssignableElements!(typeof(t0[].chunks(1)))); |
| assert(!is(typeof(transposed(t0[].chunks(1))))); |
| assert(is(typeof(transposed(t0[].chunks(1).array())))); |
| |
| auto t1 = transposed(t0[].chunks(1).array()); |
| assert(equal!"a.equal(b)"(t1, [[123]])); |
| } |
| |
| /** |
| This struct takes two ranges, $(D source) and $(D indices), and creates a view |
| of $(D source) as if its elements were reordered according to $(D indices). |
| $(D indices) may include only a subset of the elements of $(D source) and |
| may also repeat elements. |
| |
| $(D Source) must be a random access range. The returned range will be |
| bidirectional or random-access if $(D Indices) is bidirectional or |
| random-access, respectively. |
| */ |
| struct Indexed(Source, Indices) |
| if (isRandomAccessRange!Source && isInputRange!Indices && |
| is(typeof(Source.init[ElementType!(Indices).init]))) |
| { |
| this(Source source, Indices indices) |
| { |
| this._source = source; |
| this._indices = indices; |
| } |
| |
| /// Range primitives |
| @property auto ref front() |
| { |
| assert(!empty, "Attempting to fetch the front of an empty Indexed"); |
| return _source[_indices.front]; |
| } |
| |
| /// Ditto |
| void popFront() |
| { |
| assert(!empty, "Attempting to popFront an empty Indexed"); |
| _indices.popFront(); |
| } |
| |
| static if (isInfinite!Indices) |
| { |
| enum bool empty = false; |
| } |
| else |
| { |
| /// Ditto |
| @property bool empty() |
| { |
| return _indices.empty; |
| } |
| } |
| |
| static if (isForwardRange!Indices) |
| { |
| /// Ditto |
| @property typeof(this) save() |
| { |
| // Don't need to save _source because it's never consumed. |
| return typeof(this)(_source, _indices.save); |
| } |
| } |
| |
| /// Ditto |
| static if (hasAssignableElements!Source) |
| { |
| @property auto ref front(ElementType!Source newVal) |
| { |
| assert(!empty); |
| return _source[_indices.front] = newVal; |
| } |
| } |
| |
| |
| static if (hasMobileElements!Source) |
| { |
| /// Ditto |
| auto moveFront() |
| { |
| assert(!empty); |
| return _source.moveAt(_indices.front); |
| } |
| } |
| |
| static if (isBidirectionalRange!Indices) |
| { |
| /// Ditto |
| @property auto ref back() |
| { |
| assert(!empty, "Attempting to fetch the back of an empty Indexed"); |
| return _source[_indices.back]; |
| } |
| |
| /// Ditto |
| void popBack() |
| { |
| assert(!empty, "Attempting to popBack an empty Indexed"); |
| _indices.popBack(); |
| } |
| |
| /// Ditto |
| static if (hasAssignableElements!Source) |
| { |
| @property auto ref back(ElementType!Source newVal) |
| { |
| assert(!empty); |
| return _source[_indices.back] = newVal; |
| } |
| } |
| |
| |
| static if (hasMobileElements!Source) |
| { |
| /// Ditto |
| auto moveBack() |
| { |
| assert(!empty); |
| return _source.moveAt(_indices.back); |
| } |
| } |
| } |
| |
| static if (hasLength!Indices) |
| { |
| /// Ditto |
| @property size_t length() |
| { |
| return _indices.length; |
| } |
| |
| alias opDollar = length; |
| } |
| |
| static if (isRandomAccessRange!Indices) |
| { |
| /// Ditto |
| auto ref opIndex(size_t index) |
| { |
| return _source[_indices[index]]; |
| } |
| |
| static if (hasSlicing!Indices) |
| { |
| /// Ditto |
| typeof(this) opSlice(size_t a, size_t b) |
| { |
| return typeof(this)(_source, _indices[a .. b]); |
| } |
| } |
| |
| |
| static if (hasAssignableElements!Source) |
| { |
| /// Ditto |
| auto opIndexAssign(ElementType!Source newVal, size_t index) |
| { |
| return _source[_indices[index]] = newVal; |
| } |
| } |
| |
| |
| static if (hasMobileElements!Source) |
| { |
| /// Ditto |
| auto moveAt(size_t index) |
| { |
| return _source.moveAt(_indices[index]); |
| } |
| } |
| } |
| |
| // All this stuff is useful if someone wants to index an Indexed |
| // without adding a layer of indirection. |
| |
| /** |
| Returns the source range. |
| */ |
| @property Source source() |
| { |
| return _source; |
| } |
| |
| /** |
| Returns the indices range. |
| */ |
| @property Indices indices() |
| { |
| return _indices; |
| } |
| |
| static if (isRandomAccessRange!Indices) |
| { |
| /** |
| Returns the physical index into the source range corresponding to a |
| given logical index. This is useful, for example, when indexing |
| an $(D Indexed) without adding another layer of indirection. |
| */ |
| size_t physicalIndex(size_t logicalIndex) |
| { |
| return _indices[logicalIndex]; |
| } |
| |
| /// |
| @safe unittest |
| { |
| auto ind = indexed([1, 2, 3, 4, 5], [1, 3, 4]); |
| assert(ind.physicalIndex(0) == 1); |
| } |
| } |
| |
| private: |
| Source _source; |
| Indices _indices; |
| |
| } |
| |
| /// Ditto |
| Indexed!(Source, Indices) indexed(Source, Indices)(Source source, Indices indices) |
| { |
| return typeof(return)(source, indices); |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| auto source = [1, 2, 3, 4, 5]; |
| auto indices = [4, 3, 1, 2, 0, 4]; |
| auto ind = indexed(source, indices); |
| assert(equal(ind, [5, 4, 2, 3, 1, 5])); |
| assert(equal(retro(ind), [5, 1, 3, 2, 4, 5])); |
| } |
| |
| @safe unittest |
| { |
| { |
| auto ind = indexed([1, 2, 3, 4, 5], [1, 3, 4]); |
| assert(ind.physicalIndex(0) == 1); |
| } |
| |
| auto source = [1, 2, 3, 4, 5]; |
| auto indices = [4, 3, 1, 2, 0, 4]; |
| auto ind = indexed(source, indices); |
| |
| // When elements of indices are duplicated and Source has lvalue elements, |
| // these are aliased in ind. |
| ind[0]++; |
| assert(ind[0] == 6); |
| assert(ind[5] == 6); |
| } |
| |
| @safe unittest |
| { |
| import std.internal.test.dummyrange : AllDummyRanges, propagatesLength, |
| propagatesRangeType, RangeType; |
| |
| foreach (DummyType; AllDummyRanges) |
| { |
| auto d = DummyType.init; |
| auto r = indexed([1, 2, 3, 4, 5], d); |
| static assert(propagatesRangeType!(DummyType, typeof(r))); |
| static assert(propagatesLength!(DummyType, typeof(r))); |
| } |
| } |
| |
| /** |
| This range iterates over fixed-sized chunks of size $(D chunkSize) of a |
| $(D source) range. $(D Source) must be an input range. $(D chunkSize) must be |
| greater than zero. |
| |
| If $(D !isInfinite!Source) and $(D source.walkLength) is not evenly |
| divisible by $(D chunkSize), the back element of this range will contain |
| fewer than $(D chunkSize) elements. |
| |
| If `Source` is a forward range, the resulting range will be forward ranges as |
| well. Otherwise, the resulting chunks will be input ranges consuming the same |
| input: iterating over `front` will shrink the chunk such that subsequent |
| invocations of `front` will no longer return the full chunk, and calling |
| `popFront` on the outer range will invalidate any lingering references to |
| previous values of `front`. |
| |
| Params: |
| source = Range from which the chunks will be selected |
| chunkSize = Chunk size |
| |
| See_Also: $(LREF slide) |
| |
| Returns: Range of chunks. |
| */ |
| struct Chunks(Source) |
| if (isInputRange!Source) |
| { |
| static if (isForwardRange!Source) |
| { |
| /// Standard constructor |
| this(Source source, size_t chunkSize) |
| { |
| assert(chunkSize != 0, "Cannot create a Chunk with an empty chunkSize"); |
| _source = source; |
| _chunkSize = chunkSize; |
| } |
| |
| /// Input range primitives. Always present. |
| @property auto front() |
| { |
| assert(!empty, "Attempting to fetch the front of an empty Chunks"); |
| return _source.save.take(_chunkSize); |
| } |
| |
| /// Ditto |
| void popFront() |
| { |
| assert(!empty, "Attempting to popFront and empty Chunks"); |
| _source.popFrontN(_chunkSize); |
| } |
| |
| static if (!isInfinite!Source) |
| /// Ditto |
| @property bool empty() |
| { |
| return _source.empty; |
| } |
| else |
| // undocumented |
| enum empty = false; |
| |
| /// Forward range primitives. Only present if `Source` is a forward range. |
| @property typeof(this) save() |
| { |
| return typeof(this)(_source.save, _chunkSize); |
| } |
| |
| static if (hasLength!Source) |
| { |
| /// Length. Only if $(D hasLength!Source) is $(D true) |
| @property size_t length() |
| { |
| // Note: _source.length + _chunkSize may actually overflow. |
| // We cast to ulong to mitigate the problem on x86 machines. |
| // For x64 machines, we just suppose we'll never overflow. |
| // The "safe" code would require either an extra branch, or a |
| // modulo operation, which is too expensive for such a rare case |
| return cast(size_t)((cast(ulong)(_source.length) + _chunkSize - 1) / _chunkSize); |
| } |
| //Note: No point in defining opDollar here without slicing. |
| //opDollar is defined below in the hasSlicing!Source section |
| } |
| |
| static if (hasSlicing!Source) |
| { |
| //Used for various purposes |
| private enum hasSliceToEnd = is(typeof(Source.init[_chunkSize .. $]) == Source); |
| |
| /** |
| Indexing and slicing operations. Provided only if |
| $(D hasSlicing!Source) is $(D true). |
| */ |
| auto opIndex(size_t index) |
| { |
| immutable start = index * _chunkSize; |
| immutable end = start + _chunkSize; |
| |
| static if (isInfinite!Source) |
| return _source[start .. end]; |
| else |
| { |
| import std.algorithm.comparison : min; |
| immutable len = _source.length; |
| assert(start < len, "chunks index out of bounds"); |
| return _source[start .. min(end, len)]; |
| } |
| } |
| |
| /// Ditto |
| static if (hasLength!Source) |
| typeof(this) opSlice(size_t lower, size_t upper) |
| { |
| import std.algorithm.comparison : min; |
| assert(lower <= upper && upper <= length, "chunks slicing index out of bounds"); |
| immutable len = _source.length; |
| return chunks(_source[min(lower * _chunkSize, len) .. min(upper * _chunkSize, len)], _chunkSize); |
| } |
| else static if (hasSliceToEnd) |
| //For slicing an infinite chunk, we need to slice the source to the end. |
| typeof(takeExactly(this, 0)) opSlice(size_t lower, size_t upper) |
| { |
| assert(lower <= upper, "chunks slicing index out of bounds"); |
| return chunks(_source[lower * _chunkSize .. $], _chunkSize).takeExactly(upper - lower); |
| } |
| |
| static if (isInfinite!Source) |
| { |
| static if (hasSliceToEnd) |
| { |
| private static struct DollarToken{} |
| DollarToken opDollar() |
| { |
| return DollarToken(); |
| } |
| //Slice to dollar |
| typeof(this) opSlice(size_t lower, DollarToken) |
| { |
| return typeof(this)(_source[lower * _chunkSize .. $], _chunkSize); |
| } |
| } |
| } |
| else |
| { |
| //Dollar token carries a static type, with no extra information. |
| //It can lazily transform into _source.length on algorithmic |
| //operations such as : chunks[$/2, $-1]; |
| private static struct DollarToken |
| { |
| Chunks!Source* mom; |
| @property size_t momLength() |
| { |
| return mom.length; |
| } |
| alias momLength this; |
| } |
| DollarToken opDollar() |
| { |
| return DollarToken(&this); |
| } |
| |
| //Slice overloads optimized for using dollar. Without this, to slice to end, we would... |
| //1. Evaluate chunks.length |
| //2. Multiply by _chunksSize |
| //3. To finally just compare it (with min) to the original length of source (!) |
| //These overloads avoid that. |
| typeof(this) opSlice(DollarToken, DollarToken) |
| { |
| static if (hasSliceToEnd) |
| return chunks(_source[$ .. $], _chunkSize); |
| else |
| { |
| immutable len = _source.length; |
| return chunks(_source[len .. len], _chunkSize); |
| } |
| } |
| typeof(this) opSlice(size_t lower, DollarToken) |
| { |
| import std.algorithm.comparison : min; |
| assert(lower <= length, "chunks slicing index out of bounds"); |
| static if (hasSliceToEnd) |
| return chunks(_source[min(lower * _chunkSize, _source.length) .. $], _chunkSize); |
| else |
| { |
| immutable len = _source.length; |
| return chunks(_source[min(lower * _chunkSize, len) .. len], _chunkSize); |
| } |
| } |
| typeof(this) opSlice(DollarToken, size_t upper) |
| { |
| assert(upper == length, "chunks slicing index out of bounds"); |
| return this[$ .. $]; |
| } |
| } |
| } |
| |
| //Bidirectional range primitives |
| static if (hasSlicing!Source && hasLength!Source) |
| { |
| /** |
| Bidirectional range primitives. Provided only if both |
| $(D hasSlicing!Source) and $(D hasLength!Source) are $(D true). |
| */ |
| @property auto back() |
| { |
| assert(!empty, "back called on empty chunks"); |
| immutable len = _source.length; |
| immutable start = (len - 1) / _chunkSize * _chunkSize; |
| return _source[start .. len]; |
| } |
| |
| /// Ditto |
| void popBack() |
| { |
| assert(!empty, "popBack() called on empty chunks"); |
| immutable end = (_source.length - 1) / _chunkSize * _chunkSize; |
| _source = _source[0 .. end]; |
| } |
| } |
| |
| private: |
| Source _source; |
| size_t _chunkSize; |
| } |
| else // is input range only |
| { |
| import std.typecons : RefCounted; |
| |
| static struct Chunk |
| { |
| private RefCounted!Impl impl; |
| |
| @property bool empty() { return impl.curSizeLeft == 0 || impl.r.empty; } |
| @property auto front() { return impl.r.front; } |
| void popFront() |
| { |
| assert(impl.curSizeLeft > 0 && !impl.r.empty); |
| impl.curSizeLeft--; |
| impl.r.popFront(); |
| } |
| } |
| |
| static struct Impl |
| { |
| private Source r; |
| private size_t chunkSize; |
| private size_t curSizeLeft; |
| } |
| |
| private RefCounted!Impl impl; |
| |
| private this(Source r, size_t chunkSize) |
| { |
| impl = RefCounted!Impl(r, r.empty ? 0 : chunkSize, chunkSize); |
| } |
| |
| @property bool empty() { return impl.chunkSize == 0; } |
| @property Chunk front() return { return Chunk(impl); } |
| |
| void popFront() |
| { |
| impl.curSizeLeft -= impl.r.popFrontN(impl.curSizeLeft); |
| if (!impl.r.empty) |
| impl.curSizeLeft = impl.chunkSize; |
| else |
| impl.chunkSize = 0; |
| } |
| |
| static assert(isInputRange!(typeof(this))); |
| } |
| } |
| |
| /// Ditto |
| Chunks!Source chunks(Source)(Source source, size_t chunkSize) |
| if (isInputRange!Source) |
| { |
| return typeof(return)(source, chunkSize); |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| auto source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; |
| auto chunks = chunks(source, 4); |
| assert(chunks[0] == [1, 2, 3, 4]); |
| assert(chunks[1] == [5, 6, 7, 8]); |
| assert(chunks[2] == [9, 10]); |
| assert(chunks.back == chunks[2]); |
| assert(chunks.front == chunks[0]); |
| assert(chunks.length == 3); |
| assert(equal(retro(array(chunks)), array(retro(chunks)))); |
| } |
| |
| /// Non-forward input ranges are supported, but with limited semantics. |
| @system /*@safe*/ unittest // FIXME: can't be @safe because RefCounted isn't. |
| { |
| import std.algorithm.comparison : equal; |
| |
| int i; |
| |
| // The generator doesn't save state, so it cannot be a forward range. |
| auto inputRange = generate!(() => ++i).take(10); |
| |
| // We can still process it in chunks, but it will be single-pass only. |
| auto chunked = inputRange.chunks(2); |
| |
| assert(chunked.front.equal([1, 2])); |
| assert(chunked.front.empty); // Iterating the chunk has consumed it |
| chunked.popFront; |
| assert(chunked.front.equal([3, 4])); |
| } |
| |
| @system /*@safe*/ unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.internal.test.dummyrange : ReferenceInputRange; |
| |
| auto data = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]; |
| auto r = new ReferenceInputRange!int(data).chunks(3); |
| assert(r.equal!equal([ |
| [ 1, 2, 3 ], |
| [ 4, 5, 6 ], |
| [ 7, 8, 9 ], |
| [ 10 ] |
| ])); |
| |
| auto data2 = [ 1, 2, 3, 4, 5, 6 ]; |
| auto r2 = new ReferenceInputRange!int(data2).chunks(3); |
| assert(r2.equal!equal([ |
| [ 1, 2, 3 ], |
| [ 4, 5, 6 ] |
| ])); |
| |
| auto data3 = [ 1, 2, 3, 4, 5 ]; |
| auto r3 = new ReferenceInputRange!int(data3).chunks(2); |
| assert(r3.front.equal([1, 2])); |
| r3.popFront(); |
| assert(!r3.empty); |
| r3.popFront(); |
| assert(r3.front.equal([5])); |
| } |
| |
| @safe unittest |
| { |
| auto source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; |
| auto chunks = chunks(source, 4); |
| auto chunks2 = chunks.save; |
| chunks.popFront(); |
| assert(chunks[0] == [5, 6, 7, 8]); |
| assert(chunks[1] == [9, 10]); |
| chunks2.popBack(); |
| assert(chunks2[1] == [5, 6, 7, 8]); |
| assert(chunks2.length == 2); |
| |
| static assert(isRandomAccessRange!(typeof(chunks))); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| //Extra toying with slicing and indexing. |
| auto chunks1 = [0, 0, 1, 1, 2, 2, 3, 3, 4].chunks(2); |
| auto chunks2 = [0, 0, 1, 1, 2, 2, 3, 3, 4, 4].chunks(2); |
| |
| assert(chunks1.length == 5); |
| assert(chunks2.length == 5); |
| assert(chunks1[4] == [4]); |
| assert(chunks2[4] == [4, 4]); |
| assert(chunks1.back == [4]); |
| assert(chunks2.back == [4, 4]); |
| |
| assert(chunks1[0 .. 1].equal([[0, 0]])); |
| assert(chunks1[0 .. 2].equal([[0, 0], [1, 1]])); |
| assert(chunks1[4 .. 5].equal([[4]])); |
| assert(chunks2[4 .. 5].equal([[4, 4]])); |
| |
| assert(chunks1[0 .. 0].equal((int[][]).init)); |
| assert(chunks1[5 .. 5].equal((int[][]).init)); |
| assert(chunks2[5 .. 5].equal((int[][]).init)); |
| |
| //Fun with opDollar |
| assert(chunks1[$ .. $].equal((int[][]).init)); //Quick |
| assert(chunks2[$ .. $].equal((int[][]).init)); //Quick |
| assert(chunks1[$ - 1 .. $].equal([[4]])); //Semiquick |
| assert(chunks2[$ - 1 .. $].equal([[4, 4]])); //Semiquick |
| assert(chunks1[$ .. 5].equal((int[][]).init)); //Semiquick |
| assert(chunks2[$ .. 5].equal((int[][]).init)); //Semiquick |
| |
| assert(chunks1[$ / 2 .. $ - 1].equal([[2, 2], [3, 3]])); //Slow |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : filter; |
| |
| //ForwardRange |
| auto r = filter!"true"([1, 2, 3, 4, 5]).chunks(2); |
| assert(equal!"equal(a, b)"(r, [[1, 2], [3, 4], [5]])); |
| |
| //InfiniteRange w/o RA |
| auto fibsByPairs = recurrence!"a[n-1] + a[n-2]"(1, 1).chunks(2); |
| assert(equal!`equal(a, b)`(fibsByPairs.take(2), [[ 1, 1], [ 2, 3]])); |
| |
| //InfiniteRange w/ RA and slicing |
| auto odds = sequence!("a[0] + n * a[1]")(1, 2); |
| auto oddsByPairs = odds.chunks(2); |
| assert(equal!`equal(a, b)`(oddsByPairs.take(2), [[ 1, 3], [ 5, 7]])); |
| |
| //Requires phobos#991 for Sequence to have slice to end |
| static assert(hasSlicing!(typeof(odds))); |
| assert(equal!`equal(a, b)`(oddsByPairs[3 .. 5], [[13, 15], [17, 19]])); |
| assert(equal!`equal(a, b)`(oddsByPairs[3 .. $].take(2), [[13, 15], [17, 19]])); |
| } |
| |
| |
| |
| /** |
| This range splits a $(D source) range into $(D chunkCount) chunks of |
| approximately equal length. $(D Source) must be a forward range with |
| known length. |
| |
| Unlike $(LREF chunks), $(D evenChunks) takes a chunk count (not size). |
| The returned range will contain zero or more $(D source.length / |
| chunkCount + 1) elements followed by $(D source.length / chunkCount) |
| elements. If $(D source.length < chunkCount), some chunks will be empty. |
| |
| $(D chunkCount) must not be zero, unless $(D source) is also empty. |
| */ |
| struct EvenChunks(Source) |
| if (isForwardRange!Source && hasLength!Source) |
| { |
| /// Standard constructor |
| this(Source source, size_t chunkCount) |
| { |
| assert(chunkCount != 0 || source.empty, "Cannot create EvenChunks with a zero chunkCount"); |
| _source = source; |
| _chunkCount = chunkCount; |
| } |
| |
| /// Forward range primitives. Always present. |
| @property auto front() |
| { |
| assert(!empty, "Attempting to fetch the front of an empty evenChunks"); |
| return _source.save.take(_chunkPos(1)); |
| } |
| |
| /// Ditto |
| void popFront() |
| { |
| assert(!empty, "Attempting to popFront an empty evenChunks"); |
| _source.popFrontN(_chunkPos(1)); |
| _chunkCount--; |
| } |
| |
| /// Ditto |
| @property bool empty() |
| { |
| return _source.empty; |
| } |
| |
| /// Ditto |
| @property typeof(this) save() |
| { |
| return typeof(this)(_source.save, _chunkCount); |
| } |
| |
| /// Length |
| @property size_t length() const |
| { |
| return _chunkCount; |
| } |
| //Note: No point in defining opDollar here without slicing. |
| //opDollar is defined below in the hasSlicing!Source section |
| |
| static if (hasSlicing!Source) |
| { |
| /** |
| Indexing, slicing and bidirectional operations and range primitives. |
| Provided only if $(D hasSlicing!Source) is $(D true). |
| */ |
| auto opIndex(size_t index) |
| { |
| assert(index < _chunkCount, "evenChunks index out of bounds"); |
| return _source[_chunkPos(index) .. _chunkPos(index+1)]; |
| } |
| |
| /// Ditto |
| typeof(this) opSlice(size_t lower, size_t upper) |
| { |
| assert(lower <= upper && upper <= length, "evenChunks slicing index out of bounds"); |
| return evenChunks(_source[_chunkPos(lower) .. _chunkPos(upper)], upper - lower); |
| } |
| |
| /// Ditto |
| @property auto back() |
| { |
| assert(!empty, "back called on empty evenChunks"); |
| return _source[_chunkPos(_chunkCount - 1) .. _source.length]; |
| } |
| |
| /// Ditto |
| void popBack() |
| { |
| assert(!empty, "popBack() called on empty evenChunks"); |
| _source = _source[0 .. _chunkPos(_chunkCount - 1)]; |
| _chunkCount--; |
| } |
| } |
| |
| private: |
| Source _source; |
| size_t _chunkCount; |
| |
| size_t _chunkPos(size_t i) |
| { |
| /* |
| _chunkCount = 5, _source.length = 13: |
| |
| chunk0 |
| | chunk3 |
| | | |
| v v |
| +-+-+-+-+-+ ^ |
| |0|3|.| | | | |
| +-+-+-+-+-+ | div |
| |1|4|.| | | | |
| +-+-+-+-+-+ v |
| |2|5|.| |
| +-+-+-+ |
| |
| <-----> |
| mod |
| |
| <---------> |
| _chunkCount |
| |
| One column is one chunk. |
| popFront and popBack pop the left-most |
| and right-most column, respectively. |
| */ |
| |
| auto div = _source.length / _chunkCount; |
| auto mod = _source.length % _chunkCount; |
| auto pos = i <= mod |
| ? i * (div+1) |
| : mod * (div+1) + (i-mod) * div |
| ; |
| //auto len = i < mod |
| // ? div+1 |
| // : div |
| //; |
| return pos; |
| } |
| } |
| |
| /// Ditto |
| EvenChunks!Source evenChunks(Source)(Source source, size_t chunkCount) |
| if (isForwardRange!Source && hasLength!Source) |
| { |
| return typeof(return)(source, chunkCount); |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| auto source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; |
| auto chunks = evenChunks(source, 3); |
| assert(chunks[0] == [1, 2, 3, 4]); |
| assert(chunks[1] == [5, 6, 7]); |
| assert(chunks[2] == [8, 9, 10]); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| auto source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; |
| auto chunks = evenChunks(source, 3); |
| assert(chunks.back == chunks[2]); |
| assert(chunks.front == chunks[0]); |
| assert(chunks.length == 3); |
| assert(equal(retro(array(chunks)), array(retro(chunks)))); |
| |
| auto chunks2 = chunks.save; |
| chunks.popFront(); |
| assert(chunks[0] == [5, 6, 7]); |
| assert(chunks[1] == [8, 9, 10]); |
| chunks2.popBack(); |
| assert(chunks2[1] == [5, 6, 7]); |
| assert(chunks2.length == 2); |
| |
| static assert(isRandomAccessRange!(typeof(chunks))); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| int[] source = []; |
| auto chunks = source.evenChunks(0); |
| assert(chunks.length == 0); |
| chunks = source.evenChunks(3); |
| assert(equal(chunks, [[], [], []])); |
| chunks = [1, 2, 3].evenChunks(5); |
| assert(equal(chunks, [[1], [2], [3], [], []])); |
| } |
| |
| /* |
| A fixed-sized sliding window iteration |
| of size `windowSize` over a `source` range by a custom `stepSize`. |
| |
| The `Source` range must be at least an `ForwardRange` and |
| the `windowSize` must be greater than zero. |
| |
| For `windowSize = 1` it splits the range into single element groups (aka `unflatten`) |
| For `windowSize = 2` it is similar to `zip(source, source.save.dropOne)`. |
| |
| Params: |
| f = If `Yes.withFewerElements` slide with fewer |
| elements than `windowSize`. This can only happen if the initial range |
| contains less elements than `windowSize`. In this case |
| if `No.withFewerElements` an empty range will be returned. |
| source = Range from which the slide will be selected |
| windowSize = Sliding window size |
| stepSize = Steps between the windows (by default 1) |
| |
| Returns: Range of all sliding windows with propagated bi-directionality, |
| forwarding, conditional random access, and slicing. |
| |
| See_Also: $(LREF chunks) |
| */ |
| // Explicitly set to private to delay the release until 2.076 |
| private |
| auto slide(Flag!"withFewerElements" f = Yes.withFewerElements, |
| Source)(Source source, size_t windowSize, size_t stepSize = 1) |
| if (isForwardRange!Source) |
| { |
| return Slides!(f, Source)(source, windowSize, stepSize); |
| } |
| |
| private struct Slides(Flag!"withFewerElements" withFewerElements = Yes.withFewerElements, Source) |
| if (isForwardRange!Source) |
| { |
| private: |
| Source _source; |
| size_t _windowSize; |
| size_t _stepSize; |
| |
| static if (hasLength!Source) |
| { |
| enum needsEndTracker = false; |
| } |
| else |
| { |
| // if there's no information about the length, track needs to be kept manually |
| Source _nextSource; |
| enum needsEndTracker = true; |
| } |
| |
| bool _empty; |
| |
| static if (hasSlicing!Source) |
| { |
| enum hasSliceToEnd = hasSlicing!Source && is(typeof(Source.init[0 .. $]) == Source); |
| } |
| |
| public: |
| /// Standard constructor |
| this(Source source, size_t windowSize, size_t stepSize) |
| { |
| assert(windowSize > 0, "windowSize must be greater than zero"); |
| assert(stepSize > 0, "stepSize must be greater than zero"); |
| _source = source; |
| _windowSize = windowSize; |
| _stepSize = stepSize; |
| |
| static if (needsEndTracker) |
| { |
| // _nextSource is used to "look into the future" and check for the end |
| _nextSource = source.save; |
| _nextSource.popFrontN(windowSize); |
| } |
| |
| static if (!withFewerElements) |
| { |
| // empty source range is needed, s.t. length, slicing etc. works properly |
| static if (needsEndTracker) |
| { |
| if (_nextSource.empty) |
| _source = _nextSource; |
| } |
| else |
| { |
| if (_source.length < windowSize) |
| { |
| static if (hasSlicing!Source) |
| { |
| // if possible use the faster opDollar overload |
| static if (hasSliceToEnd) |
| _source = _source[$ .. $]; |
| else |
| _source = _source[_source.length .. _source.length]; |
| } |
| else |
| { |
| _source.popFrontN(_source.length); |
| } |
| } |
| } |
| } |
| |
| _empty = _source.empty; |
| } |
| |
| /// Forward range primitives. Always present. |
| @property auto front() |
| { |
| assert(!empty, "Attempting to access front on an empty slide"); |
| static if (hasSlicing!Source && hasLength!Source) |
| { |
| import std.algorithm.comparison : min; |
| return _source[0 .. min(_windowSize, _source.length)]; |
| } |
| else |
| { |
| return _source.save.take(_windowSize); |
| } |
| } |
| |
| /// Ditto |
| void popFront() |
| { |
| assert(!empty, "Attempting to call popFront() on an empty slide"); |
| _source.popFrontN(_stepSize); |
| |
| // if the range has less elements than its window size, |
| // we have seen the last full window (i.e. its empty) |
| static if (needsEndTracker) |
| { |
| if (_nextSource.empty) |
| _empty = true; |
| else |
| _nextSource.popFrontN(_stepSize); |
| } |
| else |
| { |
| if (_source.length < _windowSize) |
| _empty = true; |
| } |
| } |
| |
| static if (!isInfinite!Source) |
| { |
| /// Ditto |
| @property bool empty() const |
| { |
| return _empty; |
| } |
| } |
| else |
| { |
| // undocumented |
| enum empty = false; |
| } |
| |
| /// Ditto |
| @property typeof(this) save() |
| { |
| return typeof(this)(_source.save, _windowSize, _stepSize); |
| } |
| |
| static if (hasLength!Source) |
| { |
| /// Length. Only if $(D hasLength!Source) is $(D true) |
| @property size_t length() |
| { |
| if (_source.length < _windowSize) |
| { |
| static if (withFewerElements) |
| return 1; |
| else |
| return 0; |
| } |
| else |
| { |
| return (_source.length - _windowSize + _stepSize) / _stepSize; |
| } |
| } |
| } |
| |
| static if (hasSlicing!Source) |
| { |
| /** |
| Indexing and slicing operations. Provided only if |
| `hasSlicing!Source` is `true`. |
| */ |
| auto opIndex(size_t index) |
| { |
| immutable start = index * _stepSize; |
| |
| static if (isInfinite!Source) |
| { |
| immutable end = start + _windowSize; |
| } |
| else |
| { |
| import std.algorithm.comparison : min; |
| |
| immutable len = _source.length; |
| assert(start < len, "slide index out of bounds"); |
| immutable end = min(start + _windowSize, len); |
| } |
| |
| return _source[start .. end]; |
| } |
| |
| static if (!isInfinite!Source) |
| { |
| /// ditto |
| typeof(this) opSlice(size_t lower, size_t upper) |
| { |
| import std.algorithm.comparison : min; |
| assert(lower <= upper && upper <= length, "slide slicing index out of bounds"); |
| |
| lower *= _stepSize; |
| upper *= _stepSize; |
| |
| immutable len = _source.length; |
| |
| /* |
| * Notice that we only need to move for windowSize - 1 to the right: |
| * source = [0, 1, 2, 3] (length: 4) |
| * - source.slide(2) -> s = [[0, 1], [1, 2], [2, 3]] |
| * right pos for s[0 .. 3]: 3 (upper) + 2 (windowSize) - 1 = 4 |
| * |
| * - source.slide(3) -> s = [[0, 1, 2], [1, 2, 3]] |
| * right pos for s[0 .. 2]: 2 (upper) + 3 (windowSize) - 1 = 4 |
| * |
| * source = [0, 1, 2, 3, 4] (length: 5) |
| * - source.slide(4) -> s = [[0, 1, 2, 3], [1, 2, 3, 4]] |
| * right pos for s[0 .. 2]: 2 (upper) + 4 (windowSize) - 1 = 5 |
| */ |
| return typeof(this) |
| (_source[min(lower, len) .. min(upper + _windowSize - 1, len)], |
| _windowSize, _stepSize); |
| } |
| } |
| else static if (hasSliceToEnd) |
| { |
| //For slicing an infinite chunk, we need to slice the source to the infinite end. |
| auto opSlice(size_t lower, size_t upper) |
| { |
| assert(lower <= upper, "slide slicing index out of bounds"); |
| return typeof(this)(_source[lower * _stepSize .. $], |
| _windowSize, _stepSize).takeExactly(upper - lower); |
| } |
| } |
| |
| static if (isInfinite!Source) |
| { |
| static if (hasSliceToEnd) |
| { |
| private static struct DollarToken{} |
| DollarToken opDollar() |
| { |
| return DollarToken(); |
| } |
| //Slice to dollar |
| typeof(this) opSlice(size_t lower, DollarToken) |
| { |
| return typeof(this)(_source[lower * _stepSize .. $], _windowSize, _stepSize); |
| } |
| } |
| } |
| else |
| { |
| //Dollar token carries a static type, with no extra information. |
| //It can lazily transform into _source.length on algorithmic |
| //operations such as : slide[$/2, $-1]; |
| private static struct DollarToken |
| { |
| private size_t _length; |
| alias _length this; |
| } |
| |
| DollarToken opDollar() |
| { |
| return DollarToken(this.length); |
| } |
| |
| // Optimized slice overloads optimized for using dollar. |
| typeof(this) opSlice(DollarToken, DollarToken) |
| { |
| static if (hasSliceToEnd) |
| { |
| return typeof(this)(_source[$ .. $], _windowSize, _stepSize); |
| } |
| else |
| { |
| immutable len = _source.length; |
| return typeof(this)(_source[len .. len], _windowSize, _stepSize); |
| } |
| } |
| |
| // Optimized slice overloads optimized for using dollar. |
| typeof(this) opSlice(size_t lower, DollarToken) |
| { |
| import std.algorithm.comparison : min; |
| assert(lower <= length, "slide slicing index out of bounds"); |
| lower *= _stepSize; |
| static if (hasSliceToEnd) |
| { |
| return typeof(this)(_source[min(lower, _source.length) .. $], _windowSize, _stepSize); |
| } |
| else |
| { |
| immutable len = _source.length; |
| return typeof(this)(_source[min(lower, len) .. len], _windowSize, _stepSize); |
| } |
| } |
| |
| // Optimized slice overloads optimized for using dollar. |
| typeof(this) opSlice(DollarToken, size_t upper) |
| { |
| assert(upper == length, "slide slicing index out of bounds"); |
| return this[$ .. $]; |
| } |
| } |
| |
| // Bidirectional range primitives |
| static if (!isInfinite!Source) |
| { |
| /** |
| Bidirectional range primitives. Provided only if both |
| `hasSlicing!Source` and `!isInfinite!Source` are `true`. |
| */ |
| @property auto back() |
| { |
| import std.algorithm.comparison : max; |
| |
| assert(!empty, "Attempting to access front on an empty slide"); |
| |
| immutable len = _source.length; |
| /* |
| * Note: |
| * - `end` in the following is the exclusive end as used in opSlice |
| * - For the trivial case with `stepSize = 1` `end` is at `len`: |
| * |
| * iota(4).slide(2) = [[0, 1], [1, 2], [2, 3] (end = 4) |
| * iota(4).slide(3) = [[0, 1, 2], [1, 2, 3]] (end = 4) |
| * |
| * - For the non-trivial cases, we need to calculate the gap |
| * between `len` and `end` - this is the number of missing elements |
| * from the input range: |
| * |
| * iota(7).slide(2, 3) = [[0, 1], [3, 4]] || <gap: 2> 6 |
| * iota(7).slide(2, 4) = [[0, 1], [4, 5]] || <gap: 1> 6 |
| * iota(7).slide(1, 5) = [[0], [5]] || <gap: 1> 6 |
| * |
| * As it can be seen `gap` can be at most `stepSize - 1` |
| * More generally the elements of the sliding window with |
| * `w = windowSize` and `s = stepSize` are: |
| * |
| * [0, w], [s, s + w], [2 * s, 2 * s + w], ... [n * s, n * s + w] |
| * |
| * We can thus calculate the gap between the `end` and `len` as: |
| * |
| * gap = len - (n * s + w) = len - w - (n * s) |
| * |
| * As we aren't interested in exact value of `n`, but the best |
| * minimal `gap` value, we can use modulo to "cut" `len - w` optimally: |
| * |
| * gap = len - w - (s - s ... - s) = (len - w) % s |
| * |
| * So for example: |
| * |
| * iota(7).slide(2, 3) = [[0, 1], [3, 4]] |
| * gap: (7 - 2) % 3 = 5 % 3 = 2 |
| * end: 7 - 2 = 5 |
| * |
| * iota(7).slide(4, 2) = [[0, 1, 2, 3], [2, 3, 4, 5]] |
| * gap: (7 - 4) % 2 = 3 % 2 = 1 |
| * end: 7 - 1 = 6 |
| */ |
| size_t gap = (len - _windowSize) % _stepSize; |
| |
| // check for underflow |
| immutable start = (len > _windowSize + gap) ? len - _windowSize - gap : 0; |
| |
| return _source[start .. len - gap]; |
| } |
| |
| /// Ditto |
| void popBack() |
| { |
| assert(!empty, "Attempting to call popBack() on an empty slide"); |
| |
| immutable end = _source.length > _stepSize ? _source.length - _stepSize : 0; |
| _source = _source[0 .. end]; |
| |
| if (_source.length < _windowSize) |
| _empty = true; |
| } |
| } |
| } |
| } |
| |
| // |
| @safe pure nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.array : array; |
| |
| assert([0, 1, 2, 3].slide(2).equal!equal( |
| [[0, 1], [1, 2], [2, 3]] |
| )); |
| assert(5.iota.slide(3).equal!equal( |
| [[0, 1, 2], [1, 2, 3], [2, 3, 4]] |
| )); |
| |
| assert(iota(7).slide(2, 2).equal!equal([[0, 1], [2, 3], [4, 5]])); |
| assert(iota(12).slide(2, 4).equal!equal([[0, 1], [4, 5], [8, 9]])); |
| |
| // set a custom stepsize (default 1) |
| assert(6.iota.slide(1, 2).equal!equal( |
| [[0], [2], [4]] |
| )); |
| |
| assert(6.iota.slide(2, 4).equal!equal( |
| [[0, 1], [4, 5]] |
| )); |
| |
| // allow slide with less elements than the window size |
| assert(3.iota.slide!(No.withFewerElements)(4).empty); |
| assert(3.iota.slide!(Yes.withFewerElements)(4).equal!equal( |
| [[0, 1, 2]] |
| )); |
| } |
| |
| // count k-mers |
| @safe pure nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : each; |
| |
| int[dstring] d; |
| "AGAGA"d.slide(2).each!(a => d[a]++); |
| assert(d == ["AG"d: 2, "GA"d: 2]); |
| } |
| |
| // @nogc |
| @safe pure nothrow @nogc unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| static immutable res1 = [[0], [1], [2], [3]]; |
| assert(4.iota.slide(1).equal!equal(res1)); |
| |
| static immutable res2 = [[0, 1], [1, 2], [2, 3]]; |
| assert(4.iota.slide(2).equal!equal(res2)); |
| } |
| |
| // different window sizes |
| @safe pure nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.array : array; |
| |
| assert([0, 1, 2, 3].slide(1).array == [[0], [1], [2], [3]]); |
| assert([0, 1, 2, 3].slide(2).array == [[0, 1], [1, 2], [2, 3]]); |
| assert([0, 1, 2, 3].slide(3).array == [[0, 1, 2], [1, 2, 3]]); |
| assert([0, 1, 2, 3].slide(4).array == [[0, 1, 2, 3]]); |
| assert([0, 1, 2, 3].slide(5).array == [[0, 1, 2, 3]]); |
| |
| |
| assert(iota(2).slide(2).front.equal([0, 1])); |
| assert(iota(3).slide(2).equal!equal([[0, 1],[1, 2]])); |
| assert(iota(3).slide(3).equal!equal([[0, 1, 2]])); |
| assert(iota(3).slide(4).equal!equal([[0, 1, 2]])); |
| assert(iota(1, 4).slide(1).equal!equal([[1], [2], [3]])); |
| assert(iota(1, 4).slide(3).equal!equal([[1, 2, 3]])); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| assert(6.iota.slide(1, 1).equal!equal( |
| [[0], [1], [2], [3], [4], [5]] |
| )); |
| assert(6.iota.slide(1, 2).equal!equal( |
| [[0], [2], [4]] |
| )); |
| assert(6.iota.slide(1, 3).equal!equal( |
| [[0], [3]] |
| )); |
| assert(6.iota.slide(1, 4).equal!equal( |
| [[0], [4]] |
| )); |
| assert(6.iota.slide(1, 5).equal!equal( |
| [[0], [5]] |
| )); |
| assert(6.iota.slide(2, 1).equal!equal( |
| [[0, 1], [1, 2], [2, 3], [3, 4], [4, 5]] |
| )); |
| assert(6.iota.slide(2, 2).equal!equal( |
| [[0, 1], [2, 3], [4, 5]] |
| )); |
| assert(6.iota.slide(2, 3).equal!equal( |
| [[0, 1], [3, 4]] |
| )); |
| assert(6.iota.slide(2, 4).equal!equal( |
| [[0, 1], [4, 5]] |
| )); |
| assert(6.iota.slide(2, 5).equal!equal( |
| [[0, 1]] |
| )); |
| assert(6.iota.slide(3, 1).equal!equal( |
| [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5]] |
| )); |
| assert(6.iota.slide(3, 2).equal!equal( |
| [[0, 1, 2], [2, 3, 4]] |
| )); |
| assert(6.iota.slide(3, 3).equal!equal( |
| [[0, 1, 2], [3, 4, 5]] |
| )); |
| assert(6.iota.slide(3, 4).equal!equal( |
| [[0, 1, 2]] |
| )); |
| assert(6.iota.slide(4, 1).equal!equal( |
| [[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5]] |
| )); |
| assert(6.iota.slide(4, 2).equal!equal( |
| [[0, 1, 2, 3], [2, 3, 4, 5]] |
| )); |
| assert(6.iota.slide(4, 3).equal!equal( |
| [[0, 1, 2, 3]] |
| )); |
| assert(6.iota.slide(5, 1).equal!equal( |
| [[0, 1, 2, 3, 4], [1, 2, 3, 4, 5]] |
| )); |
| assert(6.iota.slide(5, 2).equal!equal( |
| [[0, 1, 2, 3, 4]] |
| )); |
| assert(6.iota.slide(5, 3).equal!equal( |
| [[0, 1, 2, 3, 4]] |
| )); |
| } |
| |
| // emptyness, copyability, strings |
| @safe pure nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : each, map; |
| |
| // check with empty input |
| int[] d; |
| assert(d.slide(2).empty); |
| assert(d.slide(2, 2).empty); |
| |
| // is copyable? |
| auto e = iota(5).slide(2); |
| e.popFront; |
| assert(e.save.equal!equal([[1, 2], [2, 3], [3, 4]])); |
| assert(e.save.equal!equal([[1, 2], [2, 3], [3, 4]])); |
| assert(e.map!"a.array".array == [[1, 2], [2, 3], [3, 4]]); |
| |
| // test with strings |
| int[dstring] f; |
| "AGAGA"d.slide(3).each!(a => f[a]++); |
| assert(f == ["AGA"d: 2, "GAG"d: 1]); |
| |
| int[dstring] g; |
| "ABCDEFG"d.slide(3, 3).each!(a => g[a]++); |
| assert(g == ["ABC"d:1, "DEF"d:1]); |
| } |
| |
| // test slicing, length |
| @safe pure nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.array : array; |
| |
| // test index |
| assert(iota(3).slide(4)[0].equal([0, 1, 2])); |
| assert(iota(5).slide(4)[1].equal([1, 2, 3, 4])); |
| assert(iota(3).slide(4, 2)[0].equal([0, 1, 2])); |
| assert(iota(5).slide(4, 2)[1].equal([2, 3, 4])); |
| assert(iota(3).slide(4, 3)[0].equal([0, 1, 2])); |
| assert(iota(5).slide(4, 3)[1].equal([3, 4,])); |
| |
| // test slicing |
| assert(iota(3).slide(4)[0 .. $].equal!equal([[0, 1, 2]])); |
| assert(iota(3).slide(2)[1 .. $].equal!equal([[1, 2]])); |
| assert(iota(1, 5).slide(2)[0 .. 1].equal!equal([[1, 2]])); |
| assert(iota(1, 5).slide(2)[0 .. 2].equal!equal([[1, 2], [2, 3]])); |
| assert(iota(1, 5).slide(3)[0 .. 1].equal!equal([[1, 2, 3]])); |
| assert(iota(1, 5).slide(3)[0 .. 2].equal!equal([[1, 2, 3], [2, 3, 4]])); |
| assert(iota(1, 6).slide(3)[2 .. 3].equal!equal([[3, 4, 5]])); |
| assert(iota(1, 5).slide(4)[0 .. 1].equal!equal([[1, 2, 3, 4]])); |
| |
| // length |
| assert(iota(3).slide(1).length == 3); |
| assert(iota(3).slide(1, 2).length == 2); |
| assert(iota(3).slide(1, 3).length == 1); |
| assert(iota(3).slide(1, 4).length == 1); |
| assert(iota(3).slide(2).length == 2); |
| assert(iota(3).slide(2, 2).length == 1); |
| assert(iota(3).slide(2, 3).length == 1); |
| assert(iota(3).slide(3).length == 1); |
| assert(iota(3).slide(3, 2).length == 1); |
| |
| // opDollar |
| assert(iota(3).slide(4)[$/2 .. $].equal!equal([[0, 1, 2]])); |
| assert(iota(3).slide(4)[$ .. $].empty); |
| assert(iota(3).slide(4)[$ .. 1].empty); |
| |
| assert(iota(5).slide(3, 1)[$/2 .. $].equal!equal([[1, 2, 3], [2, 3, 4]])); |
| assert(iota(5).slide(3, 2)[$/2 .. $].equal!equal([[2, 3, 4]])); |
| assert(iota(5).slide(3, 3)[$/2 .. $].equal!equal([[0, 1, 2]])); |
| assert(iota(3).slide(4, 3)[$ .. $].empty); |
| assert(iota(3).slide(4, 3)[$ .. 1].empty); |
| } |
| |
| // test No.withFewerElements |
| @safe pure nothrow unittest |
| { |
| assert(iota(3).slide(4).length == 1); |
| assert(iota(3).slide(4, 4).length == 1); |
| |
| assert(iota(3).slide!(No.withFewerElements)(4).empty); |
| assert(iota(3, 3).slide!(No.withFewerElements)(4).empty); |
| assert(iota(3).slide!(No.withFewerElements)(4).length == 0); |
| assert(iota(3).slide!(No.withFewerElements)(4, 4).length == 0); |
| |
| assert(iota(3).slide!(No.withFewerElements)(400).empty); |
| assert(iota(3).slide!(No.withFewerElements)(400).length == 0); |
| assert(iota(3).slide!(No.withFewerElements)(400, 10).length == 0); |
| |
| assert(iota(3).slide!(No.withFewerElements)(4)[0 .. $].empty); |
| assert(iota(3).slide!(No.withFewerElements)(4)[$ .. $].empty); |
| assert(iota(3).slide!(No.withFewerElements)(4)[$ .. 0].empty); |
| assert(iota(3).slide!(No.withFewerElements)(4)[$/2 .. $].empty); |
| |
| // with different step sizes |
| assert(iota(3).slide!(No.withFewerElements)(4, 5)[0 .. $].empty); |
| assert(iota(3).slide!(No.withFewerElements)(4, 6)[$ .. $].empty); |
| assert(iota(3).slide!(No.withFewerElements)(4, 7)[$ .. 0].empty); |
| assert(iota(3).slide!(No.withFewerElements)(4, 8)[$/2 .. $].empty); |
| } |
| |
| // test with infinite ranges |
| @safe pure nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| // InfiniteRange without RandomAccess |
| auto fibs = recurrence!"a[n-1] + a[n-2]"(1, 1); |
| assert(fibs.slide(2).take(2).equal!equal([[1, 1], [1, 2]])); |
| assert(fibs.slide(2, 3).take(2).equal!equal([[1, 1], [3, 5]])); |
| |
| // InfiniteRange with RandomAccess and slicing |
| auto odds = sequence!("a[0] + n * a[1]")(1, 2); |
| auto oddsByPairs = odds.slide(2); |
| assert(oddsByPairs.take(2).equal!equal([[ 1, 3], [ 3, 5]])); |
| assert(oddsByPairs[1].equal([3, 5])); |
| assert(oddsByPairs[4].equal([9, 11])); |
| |
| static assert(hasSlicing!(typeof(odds))); |
| assert(oddsByPairs[3 .. 5].equal!equal([[7, 9], [9, 11]])); |
| assert(oddsByPairs[3 .. $].take(2).equal!equal([[7, 9], [9, 11]])); |
| |
| auto oddsWithGaps = odds.slide(2, 4); |
| assert(oddsWithGaps.take(3).equal!equal([[1, 3], [9, 11], [17, 19]])); |
| assert(oddsWithGaps[2].equal([17, 19])); |
| assert(oddsWithGaps[1 .. 3].equal!equal([[9, 11], [17, 19]])); |
| assert(oddsWithGaps[1 .. $].take(2).equal!equal([[9, 11], [17, 19]])); |
| } |
| |
| // test reverse |
| @safe pure nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| auto e = iota(3).slide(2); |
| assert(e.retro.equal!equal([[1, 2], [0, 1]])); |
| assert(e.retro.array.equal(e.array.retro)); |
| |
| auto e2 = iota(5).slide(3); |
| assert(e2.retro.equal!equal([[2, 3, 4], [1, 2, 3], [0, 1, 2]])); |
| assert(e2.retro.array.equal(e2.array.retro)); |
| |
| auto e3 = iota(3).slide(4); |
| assert(e3.retro.equal!equal([[0, 1, 2]])); |
| assert(e3.retro.array.equal(e3.array.retro)); |
| } |
| |
| // test reverse with different steps |
| @safe pure nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| assert(iota(7).slide(2, 1).retro.equal!equal( |
| [[5, 6], [4, 5], [3, 4], [2, 3], [1, 2], [0, 1]] |
| )); |
| assert(iota(7).slide(2, 2).retro.equal!equal( |
| [[4, 5], [2, 3], [0, 1]] |
| )); |
| assert(iota(7).slide(2, 3).retro.equal!equal( |
| [[3, 4], [0, 1]] |
| )); |
| assert(iota(7).slide(2, 4).retro.equal!equal( |
| [[4, 5], [0, 1]] |
| )); |
| assert(iota(7).slide(2, 5).retro.equal!equal( |
| [[5, 6], [0, 1]] |
| )); |
| assert(iota(7).slide(3, 1).retro.equal!equal( |
| [[4, 5, 6], [3, 4, 5], [2, 3, 4], [1, 2, 3], [0, 1, 2]] |
| )); |
| assert(iota(7).slide(3, 2).retro.equal!equal( |
| [[4, 5, 6], [2, 3, 4], [0, 1, 2]] |
| )); |
| assert(iota(7).slide(4, 1).retro.equal!equal( |
| [[3, 4, 5, 6], [2, 3, 4, 5], [1, 2, 3, 4], [0, 1, 2, 3]] |
| )); |
| assert(iota(7).slide(4, 2).retro.equal!equal( |
| [[2, 3, 4, 5], [0, 1, 2, 3]] |
| )); |
| assert(iota(7).slide(4, 3).retro.equal!equal( |
| [[3, 4, 5, 6], [0, 1, 2, 3]] |
| )); |
| assert(iota(7).slide(4, 4).retro.equal!equal( |
| [[0, 1, 2, 3]] |
| )); |
| assert(iota(7).slide(5, 1).retro.equal!equal( |
| [[2, 3, 4, 5, 6], [1, 2, 3, 4, 5], [0, 1, 2, 3, 4]] |
| )); |
| assert(iota(7).slide(5, 2).retro.equal!equal( |
| [[2, 3, 4, 5, 6], [0, 1, 2, 3, 4]] |
| )); |
| assert(iota(7).slide(5, 3).retro.equal!equal( |
| [[0, 1, 2, 3, 4]] |
| )); |
| assert(iota(7).slide(5, 4).retro.equal!equal( |
| [[0, 1, 2, 3, 4]] |
| )); |
| } |
| |
| // step size |
| @safe pure nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| assert(iota(7).slide(2, 2).equal!equal([[0, 1], [2, 3], [4, 5]])); |
| assert(iota(8).slide(2, 2).equal!equal([[0, 1], [2, 3], [4, 5], [6, 7]])); |
| assert(iota(9).slide(2, 2).equal!equal([[0, 1], [2, 3], [4, 5], [6, 7]])); |
| assert(iota(12).slide(2, 4).equal!equal([[0, 1], [4, 5], [8, 9]])); |
| assert(iota(13).slide(2, 4).equal!equal([[0, 1], [4, 5], [8, 9]])); |
| } |
| |
| // test with dummy ranges |
| @safe pure nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.internal.test.dummyrange : DummyRange, Length, RangeType, ReturnBy, AllDummyRanges; |
| import std.meta : AliasSeq; |
| |
| alias AllForwardDummyRanges = AliasSeq!( |
| DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Forward), |
| DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Bidirectional), |
| DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random), |
| DummyRange!(ReturnBy.Reference, Length.No, RangeType.Forward), |
| DummyRange!(ReturnBy.Reference, Length.No, RangeType.Bidirectional), |
| //DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Input), |
| DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Forward), |
| DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Bidirectional), |
| DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random), |
| //DummyRange!(ReturnBy.Value, Length.No, RangeType.Input), |
| DummyRange!(ReturnBy.Value, Length.No, RangeType.Forward), |
| DummyRange!(ReturnBy.Value, Length.No, RangeType.Bidirectional) |
| ); |
| |
| foreach (Range; AliasSeq!AllForwardDummyRanges) |
| { |
| Range r; |
| assert(r.slide(1).equal!equal( |
| [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10]] |
| )); |
| assert(r.slide(2).equal!equal( |
| [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 10]] |
| )); |
| assert(r.slide(3).equal!equal( |
| [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], |
| [5, 6, 7], [6, 7, 8], [7, 8, 9], [8, 9, 10]] |
| )); |
| assert(r.slide(6).equal!equal( |
| [[1, 2, 3, 4, 5, 6], [2, 3, 4, 5, 6, 7], [3, 4, 5, 6, 7, 8], |
| [4, 5, 6, 7, 8, 9], [5, 6, 7, 8, 9, 10]] |
| )); |
| assert(r.slide(15).equal!equal( |
| [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]] |
| )); |
| |
| assert(r.slide!(No.withFewerElements)(15).empty); |
| } |
| |
| alias BackwardsDummyRanges = AliasSeq!( |
| DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random), |
| DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random), |
| ); |
| |
| foreach (Range; AliasSeq!BackwardsDummyRanges) |
| { |
| Range r; |
| assert(r.slide(1).retro.equal!equal( |
| [[10], [9], [8], [7], [6], [5], [4], [3], [2], [1]] |
| )); |
| assert(r.slide(2).retro.equal!equal( |
| [[9, 10], [8, 9], [7, 8], [6, 7], [5, 6], [4, 5], [3, 4], [2, 3], [1, 2]] |
| )); |
| assert(r.slide(5).retro.equal!equal( |
| [[6, 7, 8, 9, 10], [5, 6, 7, 8, 9], [4, 5, 6, 7, 8], |
| [3, 4, 5, 6, 7], [2, 3, 4, 5, 6], [1, 2, 3, 4, 5]] |
| )); |
| assert(r.slide(15).retro.equal!equal( |
| [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]] |
| )); |
| |
| // different step sizes |
| assert(r.slide(2, 4)[2].equal([9, 10])); |
| assert(r.slide(2, 1).equal!equal( |
| [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 10]] |
| )); |
| assert(r.slide(2, 2).equal!equal( |
| [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]] |
| )); |
| assert(r.slide(2, 3).equal!equal( |
| [[1, 2], [4, 5], [7, 8]] |
| )); |
| assert(r.slide(2, 4).equal!equal( |
| [[1, 2], [5, 6], [9, 10]] |
| )); |
| |
| // front = back |
| foreach (windowSize; 1 .. 10) |
| foreach (stepSize; 1 .. 10) |
| { |
| auto slider = r.slide(windowSize, stepSize); |
| assert(slider.retro.retro.equal!equal(slider)); |
| } |
| } |
| |
| assert(iota(1, 12).slide(2, 4)[0 .. 3].equal!equal([[1, 2], [5, 6], [9, 10]])); |
| assert(iota(1, 12).slide(2, 4)[0 .. $].equal!equal([[1, 2], [5, 6], [9, 10]])); |
| assert(iota(1, 12).slide(2, 4)[$/2 .. $].equal!equal([[5, 6], [9, 10]])); |
| |
| // reverse |
| assert(iota(1, 12).slide(2, 4).retro.equal!equal([[9, 10], [5, 6], [1, 2]])); |
| } |
| |
| // test different sliceable ranges |
| @safe pure nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.internal.test.dummyrange : DummyRange, Length, RangeType, ReturnBy; |
| import std.meta : AliasSeq; |
| |
| struct SliceableRange(Range, Flag!"withOpDollar" withOpDollar = No.withOpDollar, |
| Flag!"withInfiniteness" withInfiniteness = No.withInfiniteness) |
| { |
| Range arr = 10.iota.array; // similar to DummyRange |
| @property auto save() { return typeof(this)(arr); } |
| @property auto front() { return arr[0]; } |
| void popFront() { arr.popFront(); } |
| auto opSlice(size_t i, size_t j) |
| { |
| // subslices can't be infinite |
| return SliceableRange!(Range, withOpDollar, No.withInfiniteness)(arr[i .. j]); |
| } |
| |
| static if (withInfiniteness) |
| { |
| enum empty = false; |
| } |
| else |
| { |
| @property bool empty() { return arr.empty; } |
| @property auto length() { return arr.length; } |
| } |
| |
| static if (withOpDollar) |
| { |
| static if (withInfiniteness) |
| { |
| struct Dollar {} |
| Dollar opDollar() const { return Dollar.init; } |
| |
| //Slice to dollar |
| typeof(this) opSlice(size_t lower, Dollar) |
| { |
| return typeof(this)(arr[lower .. $]); |
| } |
| |
| } |
| else |
| { |
| alias opDollar = length; |
| } |
| } |
| } |
| |
| alias T = int[]; |
| |
| alias SliceableDummyRanges = AliasSeq!( |
| DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random, T), |
| DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random, T), |
| SliceableRange!(T, No.withOpDollar, No.withInfiniteness), |
| SliceableRange!(T, Yes.withOpDollar, No.withInfiniteness), |
| SliceableRange!(T, Yes.withOpDollar, Yes.withInfiniteness), |
| ); |
| |
| foreach (Range; AliasSeq!SliceableDummyRanges) |
| { |
| Range r; |
| r.arr = 10.iota.array; // for clarity |
| |
| static assert(isForwardRange!Range); |
| enum hasSliceToEnd = hasSlicing!Range && is(typeof(Range.init[0 .. $]) == Range); |
| |
| assert(r.slide(2)[0].equal([0, 1])); |
| assert(r.slide(2)[1].equal([1, 2])); |
| |
| // saveable |
| auto s = r.slide(2); |
| assert(s[0 .. 2].equal!equal([[0, 1], [1, 2]])); |
| s.save.popFront; |
| assert(s[0 .. 2].equal!equal([[0, 1], [1, 2]])); |
| |
| assert(r.slide(3)[1 .. 3].equal!equal([[1, 2, 3], [2, 3, 4]])); |
| } |
| |
| alias SliceableDummyRangesWithoutInfinity = AliasSeq!( |
| DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random, T), |
| DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random, T), |
| SliceableRange!(T, No.withOpDollar, No.withInfiniteness), |
| SliceableRange!(T, Yes.withOpDollar, No.withInfiniteness), |
| ); |
| |
| foreach (Range; AliasSeq!SliceableDummyRangesWithoutInfinity) |
| { |
| static assert(hasSlicing!Range); |
| static assert(hasLength!Range); |
| |
| Range r; |
| r.arr = 10.iota.array; // for clarity |
| |
| assert(r.slide!(No.withFewerElements)(6).equal!equal( |
| [[0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 6], [2, 3, 4, 5, 6, 7], |
| [3, 4, 5, 6, 7, 8], [4, 5, 6, 7, 8, 9]] |
| )); |
| assert(r.slide!(No.withFewerElements)(16).empty); |
| |
| assert(r.slide(4)[0 .. $].equal(r.slide(4))); |
| assert(r.slide(2)[$/2 .. $].equal!equal([[4, 5], [5, 6], [6, 7], [7, 8], [8, 9]])); |
| assert(r.slide(2)[$ .. $].empty); |
| |
| assert(r.slide(3).retro.equal!equal( |
| [[7, 8, 9], [6, 7, 8], [5, 6, 7], [4, 5, 6], [3, 4, 5], [2, 3, 4], [1, 2, 3], [0, 1, 2]] |
| )); |
| } |
| |
| // separate checks for infinity |
| auto infIndex = SliceableRange!(T, No.withOpDollar, Yes.withInfiniteness)([0, 1, 2, 3]); |
| assert(infIndex.slide(2)[0].equal([0, 1])); |
| assert(infIndex.slide(2)[1].equal([1, 2])); |
| |
| auto infDollar = SliceableRange!(T, Yes.withOpDollar, Yes.withInfiniteness)(); |
| assert(infDollar.slide(2)[1 .. $].front.equal([1, 2])); |
| assert(infDollar.slide(4)[0 .. $].front.equal([0, 1, 2, 3])); |
| assert(infDollar.slide(4)[2 .. $].front.equal([2, 3, 4, 5])); |
| } |
| |
| private struct OnlyResult(T, size_t arity) |
| { |
| private this(Values...)(auto ref Values values) |
| { |
| this.data = [values]; |
| this.backIndex = arity; |
| } |
| |
| bool empty() @property |
| { |
| return frontIndex >= backIndex; |
| } |
| |
| T front() @property |
| { |
| assert(!empty, "Attempting to fetch the front of an empty Only range"); |
| return data[frontIndex]; |
| } |
| |
| void popFront() |
| { |
| assert(!empty, "Attempting to popFront an empty Only range"); |
| ++frontIndex; |
| } |
| |
| T back() @property |
| { |
| assert(!empty, "Attempting to fetch the back of an empty Only range"); |
| return data[backIndex - 1]; |
| } |
| |
| void popBack() |
| { |
| assert(!empty, "Attempting to popBack an empty Only range"); |
| --backIndex; |
| } |
| |
| OnlyResult save() @property |
| { |
| return this; |
| } |
| |
| size_t length() const @property |
| { |
| return backIndex - frontIndex; |
| } |
| |
| alias opDollar = length; |
| |
| T opIndex(size_t idx) |
| { |
| // when i + idx points to elements popped |
| // with popBack |
| assert(idx < length, "Attempting to fetch an out of bounds index from an Only range"); |
| return data[frontIndex + idx]; |
| } |
| |
| OnlyResult opSlice() |
| { |
| return this; |
| } |
| |
| OnlyResult opSlice(size_t from, size_t to) |
| { |
| OnlyResult result = this; |
| result.frontIndex += from; |
| result.backIndex = this.frontIndex + to; |
| assert( |
| from <= to, |
| "Attempting to slice an Only range with a larger first argument than the second." |
| ); |
| assert( |
| to <= length, |
| "Attempting to slice using an out of bounds index on an Only range" |
| ); |
| return result; |
| } |
| |
| private size_t frontIndex = 0; |
| private size_t backIndex = 0; |
| |
| // @@@BUG@@@ 10643 |
| version (none) |
| { |
| import std.traits : hasElaborateAssign; |
| static if (hasElaborateAssign!T) |
| private T[arity] data; |
| else |
| private T[arity] data = void; |
| } |
| else |
| private T[arity] data; |
| } |
| |
| // Specialize for single-element results |
| private struct OnlyResult(T, size_t arity : 1) |
| { |
| @property T front() |
| { |
| assert(!empty, "Attempting to fetch the front of an empty Only range"); |
| return _value; |
| } |
| @property T back() |
| { |
| assert(!empty, "Attempting to fetch the back of an empty Only range"); |
| return _value; |
| } |
| @property bool empty() const { return _empty; } |
| @property size_t length() const { return !_empty; } |
| @property auto save() { return this; } |
| void popFront() |
| { |
| assert(!_empty, "Attempting to popFront an empty Only range"); |
| _empty = true; |
| } |
| void popBack() |
| { |
| assert(!_empty, "Attempting to popBack an empty Only range"); |
| _empty = true; |
| } |
| alias opDollar = length; |
| |
| private this()(auto ref T value) |
| { |
| this._value = value; |
| this._empty = false; |
| } |
| |
| T opIndex(size_t i) |
| { |
| assert(!_empty && i == 0, "Attempting to fetch an out of bounds index from an Only range"); |
| return _value; |
| } |
| |
| OnlyResult opSlice() |
| { |
| return this; |
| } |
| |
| OnlyResult opSlice(size_t from, size_t to) |
| { |
| assert( |
| from <= to, |
| "Attempting to slice an Only range with a larger first argument than the second." |
| ); |
| assert( |
| to <= length, |
| "Attempting to slice using an out of bounds index on an Only range" |
| ); |
| OnlyResult copy = this; |
| copy._empty = _empty || from == to; |
| return copy; |
| } |
| |
| private Unqual!T _value; |
| private bool _empty = true; |
| } |
| |
| // Specialize for the empty range |
| private struct OnlyResult(T, size_t arity : 0) |
| { |
| private static struct EmptyElementType {} |
| |
| bool empty() @property { return true; } |
| size_t length() const @property { return 0; } |
| alias opDollar = length; |
| EmptyElementType front() @property { assert(false); } |
| void popFront() { assert(false); } |
| EmptyElementType back() @property { assert(false); } |
| void popBack() { assert(false); } |
| OnlyResult save() @property { return this; } |
| |
| EmptyElementType opIndex(size_t i) |
| { |
| assert(false); |
| } |
| |
| OnlyResult opSlice() { return this; } |
| |
| OnlyResult opSlice(size_t from, size_t to) |
| { |
| assert(from == 0 && to == 0); |
| return this; |
| } |
| } |
| |
| /** |
| Assemble $(D values) into a range that carries all its |
| elements in-situ. |
| |
| Useful when a single value or multiple disconnected values |
| must be passed to an algorithm expecting a range, without |
| having to perform dynamic memory allocation. |
| |
| As copying the range means copying all elements, it can be |
| safely returned from functions. For the same reason, copying |
| the returned range may be expensive for a large number of arguments. |
| |
| Params: |
| values = the values to assemble together |
| |
| Returns: |
| A `RandomAccessRange` of the assembled values. |
| |
| See_Also: $(LREF chain) to chain ranges |
| */ |
| auto only(Values...)(auto ref Values values) |
| if (!is(CommonType!Values == void) || Values.length == 0) |
| { |
| return OnlyResult!(CommonType!Values, Values.length)(values); |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : filter, joiner, map; |
| import std.algorithm.searching : findSplitBefore; |
| import std.uni : isUpper; |
| |
| assert(equal(only('♡'), "♡")); |
| assert([1, 2, 3, 4].findSplitBefore(only(3))[0] == [1, 2]); |
| |
| assert(only("one", "two", "three").joiner(" ").equal("one two three")); |
| |
| string title = "The D Programming Language"; |
| assert(title |
| .filter!isUpper // take the upper case letters |
| .map!only // make each letter its own range |
| .joiner(".") // join the ranges together lazily |
| .equal("T.D.P.L")); |
| } |
| |
| @safe unittest |
| { |
| // Verify that the same common type and same arity |
| // results in the same template instantiation |
| static assert(is(typeof(only(byte.init, int.init)) == |
| typeof(only(int.init, byte.init)))); |
| |
| static assert(is(typeof(only((const(char)[]).init, string.init)) == |
| typeof(only((const(char)[]).init, (const(char)[]).init)))); |
| } |
| |
| // Tests the zero-element result |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| auto emptyRange = only(); |
| |
| alias EmptyRange = typeof(emptyRange); |
| static assert(isInputRange!EmptyRange); |
| static assert(isForwardRange!EmptyRange); |
| static assert(isBidirectionalRange!EmptyRange); |
| static assert(isRandomAccessRange!EmptyRange); |
| static assert(hasLength!EmptyRange); |
| static assert(hasSlicing!EmptyRange); |
| |
| assert(emptyRange.empty); |
| assert(emptyRange.length == 0); |
| assert(emptyRange.equal(emptyRange[])); |
| assert(emptyRange.equal(emptyRange.save)); |
| assert(emptyRange[0 .. 0].equal(emptyRange)); |
| } |
| |
| // Tests the single-element result |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.typecons : tuple; |
| foreach (x; tuple(1, '1', 1.0, "1", [1])) |
| { |
| auto a = only(x); |
| typeof(x)[] e = []; |
| assert(a.front == x); |
| assert(a.back == x); |
| assert(!a.empty); |
| assert(a.length == 1); |
| assert(equal(a, a[])); |
| assert(equal(a, a[0 .. 1])); |
| assert(equal(a[0 .. 0], e)); |
| assert(equal(a[1 .. 1], e)); |
| assert(a[0] == x); |
| |
| auto b = a.save; |
| assert(equal(a, b)); |
| a.popFront(); |
| assert(a.empty && a.length == 0 && a[].empty); |
| b.popBack(); |
| assert(b.empty && b.length == 0 && b[].empty); |
| |
| alias A = typeof(a); |
| static assert(isInputRange!A); |
| static assert(isForwardRange!A); |
| static assert(isBidirectionalRange!A); |
| static assert(isRandomAccessRange!A); |
| static assert(hasLength!A); |
| static assert(hasSlicing!A); |
| } |
| |
| auto imm = only!(immutable int)(1); |
| immutable int[] imme = []; |
| assert(imm.front == 1); |
| assert(imm.back == 1); |
| assert(!imm.empty); |
| assert(imm.init.empty); // Issue 13441 |
| assert(imm.length == 1); |
| assert(equal(imm, imm[])); |
| assert(equal(imm, imm[0 .. 1])); |
| assert(equal(imm[0 .. 0], imme)); |
| assert(equal(imm[1 .. 1], imme)); |
| assert(imm[0] == 1); |
| } |
| |
| // Tests multiple-element results |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : joiner; |
| import std.meta : AliasSeq; |
| static assert(!__traits(compiles, only(1, "1"))); |
| |
| auto nums = only!(byte, uint, long)(1, 2, 3); |
| static assert(is(ElementType!(typeof(nums)) == long)); |
| assert(nums.length == 3); |
| |
| foreach (i; 0 .. 3) |
| assert(nums[i] == i + 1); |
| |
| auto saved = nums.save; |
| |
| foreach (i; 1 .. 4) |
| { |
| assert(nums.front == nums[0]); |
| assert(nums.front == i); |
| nums.popFront(); |
| assert(nums.length == 3 - i); |
| } |
| |
| assert(nums.empty); |
| |
| assert(saved.equal(only(1, 2, 3))); |
| assert(saved.equal(saved[])); |
| assert(saved[0 .. 1].equal(only(1))); |
| assert(saved[0 .. 2].equal(only(1, 2))); |
| assert(saved[0 .. 3].equal(saved)); |
| assert(saved[1 .. 3].equal(only(2, 3))); |
| assert(saved[2 .. 3].equal(only(3))); |
| assert(saved[0 .. 0].empty); |
| assert(saved[3 .. 3].empty); |
| |
| alias data = AliasSeq!("one", "two", "three", "four"); |
| static joined = |
| ["one two", "one two three", "one two three four"]; |
| string[] joinedRange = joined; |
| |
| foreach (argCount; AliasSeq!(2, 3, 4)) |
| { |
| auto values = only(data[0 .. argCount]); |
| alias Values = typeof(values); |
| static assert(is(ElementType!Values == string)); |
| static assert(isInputRange!Values); |
| static assert(isForwardRange!Values); |
| static assert(isBidirectionalRange!Values); |
| static assert(isRandomAccessRange!Values); |
| static assert(hasSlicing!Values); |
| static assert(hasLength!Values); |
| |
| assert(values.length == argCount); |
| assert(values[0 .. $].equal(values[0 .. values.length])); |
| assert(values.joiner(" ").equal(joinedRange.front)); |
| joinedRange.popFront(); |
| } |
| |
| assert(saved.retro.equal(only(3, 2, 1))); |
| assert(saved.length == 3); |
| |
| assert(saved.back == 3); |
| saved.popBack(); |
| assert(saved.length == 2); |
| assert(saved.back == 2); |
| |
| assert(saved.front == 1); |
| saved.popFront(); |
| assert(saved.length == 1); |
| assert(saved.front == 2); |
| |
| saved.popBack(); |
| assert(saved.empty); |
| |
| auto imm = only!(immutable int, immutable int)(42, 24); |
| alias Imm = typeof(imm); |
| static assert(is(ElementType!Imm == immutable(int))); |
| assert(!imm.empty); |
| assert(imm.init.empty); // Issue 13441 |
| assert(imm.front == 42); |
| imm.popFront(); |
| assert(imm.front == 24); |
| imm.popFront(); |
| assert(imm.empty); |
| |
| static struct Test { int* a; } |
| immutable(Test) test; |
| cast(void) only(test, test); // Works with mutable indirection |
| } |
| |
| /** |
| Iterate over `range` with an attached index variable. |
| |
| Each element is a $(REF Tuple, std,typecons) containing the index |
| and the element, in that order, where the index member is named $(D index) |
| and the element member is named `value`. |
| |
| The index starts at `start` and is incremented by one on every iteration. |
| |
| Overflow: |
| If `range` has length, then it is an error to pass a value for `start` |
| so that `start + range.length` is bigger than `Enumerator.max`, thus |
| it is ensured that overflow cannot happen. |
| |
| If `range` does not have length, and `popFront` is called when |
| `front.index == Enumerator.max`, the index will overflow and |
| continue from `Enumerator.min`. |
| |
| Params: |
| range = the input range to attach indexes to |
| start = the number to start the index counter from |
| |
| Returns: |
| At minimum, an input range. All other range primitives are given in the |
| resulting range if `range` has them. The exceptions are the bidirectional |
| primitives, which are propagated only if `range` has length. |
| |
| Example: |
| Useful for using $(D foreach) with an index loop variable: |
| ---- |
| import std.stdio : stdin, stdout; |
| import std.range : enumerate; |
| |
| foreach (lineNum, line; stdin.byLine().enumerate(1)) |
| stdout.writefln("line #%s: %s", lineNum, line); |
| ---- |
| */ |
| auto enumerate(Enumerator = size_t, Range)(Range range, Enumerator start = 0) |
| if (isIntegral!Enumerator && isInputRange!Range) |
| in |
| { |
| static if (hasLength!Range) |
| { |
| // TODO: core.checkedint supports mixed signedness yet? |
| import core.checkedint : adds, addu; |
| import std.conv : ConvException, to; |
| import std.traits : isSigned, Largest, Signed; |
| |
| alias LengthType = typeof(range.length); |
| bool overflow; |
| static if (isSigned!Enumerator && isSigned!LengthType) |
| auto result = adds(start, range.length, overflow); |
| else static if (isSigned!Enumerator) |
| { |
| Largest!(Enumerator, Signed!LengthType) signedLength; |
| try signedLength = to!(typeof(signedLength))(range.length); |
| catch (ConvException) |
| overflow = true; |
| catch (Exception) |
| assert(false); |
| |
| auto result = adds(start, signedLength, overflow); |
| } |
| else |
| { |
| static if (isSigned!LengthType) |
| assert(range.length >= 0); |
| auto result = addu(start, range.length, overflow); |
| } |
| |
| assert(!overflow && result <= Enumerator.max); |
| } |
| } |
| body |
| { |
| // TODO: Relax isIntegral!Enumerator to allow user-defined integral types |
| static struct Result |
| { |
| import std.typecons : Tuple; |
| |
| private: |
| alias ElemType = Tuple!(Enumerator, "index", ElementType!Range, "value"); |
| Range range; |
| Enumerator index; |
| |
| public: |
| ElemType front() @property |
| { |
| assert(!range.empty, "Attempting to fetch the front of an empty enumerate"); |
| return typeof(return)(index, range.front); |
| } |
| |
| static if (isInfinite!Range) |
| enum bool empty = false; |
| else |
| { |
| bool empty() @property |
| { |
| return range.empty; |
| } |
| } |
| |
| void popFront() |
| { |
| assert(!range.empty, "Attempting to popFront an empty enumerate"); |
| range.popFront(); |
| ++index; // When !hasLength!Range, overflow is expected |
| } |
| |
| static if (isForwardRange!Range) |
| { |
| Result save() @property |
| { |
| return typeof(return)(range.save, index); |
| } |
| } |
| |
| static if (hasLength!Range) |
| { |
| size_t length() @property |
| { |
| return range.length; |
| } |
| |
| alias opDollar = length; |
| |
| static if (isBidirectionalRange!Range) |
| { |
| ElemType back() @property |
| { |
| assert(!range.empty, "Attempting to fetch the back of an empty enumerate"); |
| return typeof(return)(cast(Enumerator)(index + range.length - 1), range.back); |
| } |
| |
| void popBack() |
| { |
| assert(!range.empty, "Attempting to popBack an empty enumerate"); |
| range.popBack(); |
| } |
| } |
| } |
| |
| static if (isRandomAccessRange!Range) |
| { |
| ElemType opIndex(size_t i) |
| { |
| return typeof(return)(cast(Enumerator)(index + i), range[i]); |
| } |
| } |
| |
| static if (hasSlicing!Range) |
| { |
| static if (hasLength!Range) |
| { |
| Result opSlice(size_t i, size_t j) |
| { |
| return typeof(return)(range[i .. j], cast(Enumerator)(index + i)); |
| } |
| } |
| else |
| { |
| static struct DollarToken {} |
| enum opDollar = DollarToken.init; |
| |
| Result opSlice(size_t i, DollarToken) |
| { |
| return typeof(return)(range[i .. $], cast(Enumerator)(index + i)); |
| } |
| |
| auto opSlice(size_t i, size_t j) |
| { |
| return this[i .. $].takeExactly(j - 1); |
| } |
| } |
| } |
| } |
| |
| return Result(range, start); |
| } |
| |
| /// Can start enumeration from a negative position: |
| pure @safe nothrow unittest |
| { |
| import std.array : assocArray; |
| import std.range : enumerate; |
| |
| bool[int] aa = true.repeat(3).enumerate(-1).assocArray(); |
| assert(aa[-1]); |
| assert(aa[0]); |
| assert(aa[1]); |
| } |
| |
| pure @safe nothrow unittest |
| { |
| import std.internal.test.dummyrange : AllDummyRanges; |
| import std.meta : AliasSeq; |
| import std.typecons : tuple; |
| |
| static struct HasSlicing |
| { |
| typeof(this) front() @property { return typeof(this).init; } |
| bool empty() @property { return true; } |
| void popFront() {} |
| |
| typeof(this) opSlice(size_t, size_t) |
| { |
| return typeof(this)(); |
| } |
| } |
| |
| foreach (DummyType; AliasSeq!(AllDummyRanges, HasSlicing)) |
| { |
| alias R = typeof(enumerate(DummyType.init)); |
| static assert(isInputRange!R); |
| static assert(isForwardRange!R == isForwardRange!DummyType); |
| static assert(isRandomAccessRange!R == isRandomAccessRange!DummyType); |
| static assert(!hasAssignableElements!R); |
| |
| static if (hasLength!DummyType) |
| { |
| static assert(hasLength!R); |
| static assert(isBidirectionalRange!R == |
| isBidirectionalRange!DummyType); |
| } |
| |
| static assert(hasSlicing!R == hasSlicing!DummyType); |
| } |
| |
| static immutable values = ["zero", "one", "two", "three"]; |
| auto enumerated = values[].enumerate(); |
| assert(!enumerated.empty); |
| assert(enumerated.front == tuple(0, "zero")); |
| assert(enumerated.back == tuple(3, "three")); |
| |
| typeof(enumerated) saved = enumerated.save; |
| saved.popFront(); |
| assert(enumerated.front == tuple(0, "zero")); |
| assert(saved.front == tuple(1, "one")); |
| assert(saved.length == enumerated.length - 1); |
| saved.popBack(); |
| assert(enumerated.back == tuple(3, "three")); |
| assert(saved.back == tuple(2, "two")); |
| saved.popFront(); |
| assert(saved.front == tuple(2, "two")); |
| assert(saved.back == tuple(2, "two")); |
| saved.popFront(); |
| assert(saved.empty); |
| |
| size_t control = 0; |
| foreach (i, v; enumerated) |
| { |
| static assert(is(typeof(i) == size_t)); |
| static assert(is(typeof(v) == typeof(values[0]))); |
| assert(i == control); |
| assert(v == values[i]); |
| assert(tuple(i, v) == enumerated[i]); |
| ++control; |
| } |
| |
| assert(enumerated[0 .. $].front == tuple(0, "zero")); |
| assert(enumerated[$ - 1 .. $].front == tuple(3, "three")); |
| |
| foreach (i; 0 .. 10) |
| { |
| auto shifted = values[0 .. 2].enumerate(i); |
| assert(shifted.front == tuple(i, "zero")); |
| assert(shifted[0] == shifted.front); |
| |
| auto next = tuple(i + 1, "one"); |
| assert(shifted[1] == next); |
| shifted.popFront(); |
| assert(shifted.front == next); |
| shifted.popFront(); |
| assert(shifted.empty); |
| } |
| |
| foreach (T; AliasSeq!(ubyte, byte, uint, int)) |
| { |
| auto inf = 42.repeat().enumerate(T.max); |
| alias Inf = typeof(inf); |
| static assert(isInfinite!Inf); |
| static assert(hasSlicing!Inf); |
| |
| // test overflow |
| assert(inf.front == tuple(T.max, 42)); |
| inf.popFront(); |
| assert(inf.front == tuple(T.min, 42)); |
| |
| // test slicing |
| inf = inf[42 .. $]; |
| assert(inf.front == tuple(T.min + 42, 42)); |
| auto window = inf[0 .. 2]; |
| assert(window.length == 1); |
| assert(window.front == inf.front); |
| window.popFront(); |
| assert(window.empty); |
| } |
| } |
| |
| pure @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.meta : AliasSeq; |
| static immutable int[] values = [0, 1, 2, 3, 4]; |
| foreach (T; AliasSeq!(ubyte, ushort, uint, ulong)) |
| { |
| auto enumerated = values.enumerate!T(); |
| static assert(is(typeof(enumerated.front.index) == T)); |
| assert(enumerated.equal(values[].zip(values))); |
| |
| foreach (T i; 0 .. 5) |
| { |
| auto subset = values[cast(size_t) i .. $]; |
| auto offsetEnumerated = subset.enumerate(i); |
| static assert(is(typeof(enumerated.front.index) == T)); |
| assert(offsetEnumerated.equal(subset.zip(subset))); |
| } |
| } |
| } |
| |
| version (none) // @@@BUG@@@ 10939 |
| { |
| // Re-enable (or remove) if 10939 is resolved. |
| /+pure+/ @safe unittest // Impure because of std.conv.to |
| { |
| import core.exception : RangeError; |
| import std.exception : assertNotThrown, assertThrown; |
| import std.meta : AliasSeq; |
| |
| static immutable values = [42]; |
| |
| static struct SignedLengthRange |
| { |
| immutable(int)[] _values = values; |
| |
| int front() @property { assert(false); } |
| bool empty() @property { assert(false); } |
| void popFront() { assert(false); } |
| |
| int length() @property |
| { |
| return cast(int)_values.length; |
| } |
| } |
| |
| SignedLengthRange svalues; |
| foreach (Enumerator; AliasSeq!(ubyte, byte, ushort, short, uint, int, ulong, long)) |
| { |
| assertThrown!RangeError(values[].enumerate!Enumerator(Enumerator.max)); |
| assertNotThrown!RangeError(values[].enumerate!Enumerator(Enumerator.max - values.length)); |
| assertThrown!RangeError(values[].enumerate!Enumerator(Enumerator.max - values.length + 1)); |
| |
| assertThrown!RangeError(svalues.enumerate!Enumerator(Enumerator.max)); |
| assertNotThrown!RangeError(svalues.enumerate!Enumerator(Enumerator.max - values.length)); |
| assertThrown!RangeError(svalues.enumerate!Enumerator(Enumerator.max - values.length + 1)); |
| } |
| |
| foreach (Enumerator; AliasSeq!(byte, short, int)) |
| { |
| assertThrown!RangeError(repeat(0, uint.max).enumerate!Enumerator()); |
| } |
| |
| assertNotThrown!RangeError(repeat(0, uint.max).enumerate!long()); |
| } |
| } |
| |
| /** |
| Returns true if $(D fn) accepts variables of type T1 and T2 in any order. |
| The following code should compile: |
| --- |
| T1 foo(); |
| T2 bar(); |
| |
| fn(foo(), bar()); |
| fn(bar(), foo()); |
| --- |
| */ |
| template isTwoWayCompatible(alias fn, T1, T2) |
| { |
| enum isTwoWayCompatible = is(typeof( (){ |
| T1 foo(); |
| T2 bar(); |
| |
| fn(foo(), bar()); |
| fn(bar(), foo()); |
| } |
| )); |
| } |
| |
| |
| /** |
| Policy used with the searching primitives $(D lowerBound), $(D |
| upperBound), and $(D equalRange) of $(LREF SortedRange) below. |
| */ |
| 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, |
| 3, 6, 10, 15, 21, 28,...) Once the search overshoots its target, |
| the remaining interval is searched using binary search. The |
| search is completed in $(BIGOH sqrt(n)) time. Use it when you |
| are reasonably confident that the value is around the beginning |
| of the range. |
| */ |
| trot, |
| |
| /** |
| Performs a $(LINK2 https://en.wikipedia.org/wiki/Exponential_search, |
| galloping search algorithm), i.e. searches |
| with a step that doubles every time, (1, 2, 4, 8, ...) leading |
| to an exponential search schedule (indexes tried are 0, 1, 3, |
| 7, 15, 31, 63,...) Once the search overshoots its target, the |
| remaining interval is searched using binary search. A value is |
| found in $(BIGOH log(n)) time. |
| */ |
| gallop, |
| |
| /** |
| Searches using a classic interval halving policy. The search |
| starts in the middle of the range, and each search step cuts |
| the range in half. This policy finds a value in $(BIGOH log(n)) |
| time but is less cache friendly than $(D gallop) for large |
| ranges. The $(D binarySearch) policy is used as the last step |
| of $(D trot), $(D gallop), $(D trotBackwards), and $(D |
| gallopBackwards) strategies. |
| */ |
| binarySearch, |
| |
| /** |
| Similar to $(D trot) but starts backwards. Use it when |
| confident that the value is around the end of the range. |
| */ |
| trotBackwards, |
| |
| /** |
| Similar to $(D gallop) but starts backwards. Use it when |
| confident that the value is around the end of the range. |
| */ |
| gallopBackwards |
| } |
| |
| /** |
| 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 |
| $(REF sort, std,algorithm,sorting) 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. |
| */ |
| struct SortedRange(Range, alias pred = "a < b") |
| if (isInputRange!Range) |
| { |
| import std.functional : binaryFun; |
| |
| private alias predFun = binaryFun!pred; |
| private bool geq(L, R)(L lhs, R rhs) |
| { |
| return !predFun(lhs, rhs); |
| } |
| private bool gt(L, R)(L lhs, R rhs) |
| { |
| return predFun(rhs, lhs); |
| } |
| private Range _input; |
| |
| // Undocummented because a clearer way to invoke is by calling |
| // assumeSorted. |
| this(Range input) |
| out |
| { |
| // moved out of the body as a workaround for Issue 12661 |
| dbgVerifySorted(); |
| } |
| body |
| { |
| this._input = input; |
| } |
| |
| // Assertion only. |
| private void dbgVerifySorted() |
| { |
| if (!__ctfe) |
| debug |
| { |
| static if (isRandomAccessRange!Range && hasLength!Range) |
| { |
| import core.bitop : bsr; |
| import std.algorithm.sorting : isSorted; |
| |
| // 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; |
| auto st = stride(this._input, step); |
| |
| assert(isSorted!pred(st), "Range is not sorted"); |
| } |
| } |
| } |
| |
| /// Range primitives. |
| @property bool empty() //const |
| { |
| return this._input.empty; |
| } |
| |
| /// Ditto |
| static if (isForwardRange!Range) |
| @property auto save() |
| { |
| // Avoid the constructor |
| typeof(this) result = this; |
| result._input = _input.save; |
| return result; |
| } |
| |
| /// Ditto |
| @property auto ref front() |
| { |
| return _input.front; |
| } |
| |
| /// Ditto |
| void popFront() |
| { |
| _input.popFront(); |
| } |
| |
| /// Ditto |
| static if (isBidirectionalRange!Range) |
| { |
| @property auto ref back() |
| { |
| return _input.back; |
| } |
| |
| /// Ditto |
| void popBack() |
| { |
| _input.popBack(); |
| } |
| } |
| |
| /// Ditto |
| static if (isRandomAccessRange!Range) |
| auto ref opIndex(size_t i) |
| { |
| return _input[i]; |
| } |
| |
| /// Ditto |
| static if (hasSlicing!Range) |
| auto opSlice(size_t a, size_t b) |
| { |
| assert( |
| a <= b, |
| "Attempting to slice a SortedRange with a larger first argument than the second." |
| ); |
| typeof(this) result = this; |
| result._input = _input[a .. b];// skip checking |
| return result; |
| } |
| |
| /// Ditto |
| static if (hasLength!Range) |
| { |
| @property size_t length() //const |
| { |
| return _input.length; |
| } |
| alias opDollar = length; |
| } |
| |
| /** |
| Releases the controlled range and returns it. |
| */ |
| auto release() |
| { |
| import std.algorithm.mutation : move; |
| return move(_input); |
| } |
| |
| // Assuming a predicate "test" that returns 0 for a left portion |
| // 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 && isRandomAccessRange!Range && hasLength!Range) |
| { |
| size_t first = 0, count = _input.length; |
| while (count > 0) |
| { |
| immutable step = count / 2, it = first + step; |
| if (!test(_input[it], v)) |
| { |
| first = it + 1; |
| count -= step + 1; |
| } |
| else |
| { |
| count = step; |
| } |
| } |
| return first; |
| } |
| |
| // Specialization for trot and gallop |
| private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v) |
| if ((sp == SearchPolicy.trot || sp == SearchPolicy.gallop) |
| && isRandomAccessRange!Range) |
| { |
| if (empty || test(front, v)) return 0; |
| immutable count = length; |
| if (count == 1) return 1; |
| size_t below = 0, above = 1, step = 2; |
| while (!test(_input[above], v)) |
| { |
| // Still too small, update below and increase gait |
| below = above; |
| immutable next = above + step; |
| if (next >= count) |
| { |
| // Overshot - the next step took us beyond the end. So |
| // now adjust next and simply exit the loop to do the |
| // binary search thingie. |
| above = count; |
| break; |
| } |
| // Still in business, increase step and continue |
| above = next; |
| static if (sp == SearchPolicy.trot) |
| ++step; |
| else |
| step <<= 1; |
| } |
| return below + this[below .. above].getTransitionIndex!( |
| SearchPolicy.binarySearch, test, V)(v); |
| } |
| |
| // Specialization for trotBackwards and gallopBackwards |
| private size_t getTransitionIndex(SearchPolicy sp, alias test, V)(V v) |
| if ((sp == SearchPolicy.trotBackwards || sp == SearchPolicy.gallopBackwards) |
| && isRandomAccessRange!Range) |
| { |
| immutable count = length; |
| if (empty || !test(back, v)) return count; |
| if (count == 1) return 0; |
| size_t below = count - 2, above = count - 1, step = 2; |
| while (test(_input[below], v)) |
| { |
| // Still too large, update above and increase gait |
| above = below; |
| if (below < step) |
| { |
| // Overshot - the next step took us beyond the end. So |
| // now adjust next and simply fall through to do the |
| // binary search thingie. |
| below = 0; |
| break; |
| } |
| // Still in business, increase step and continue |
| below -= step; |
| static if (sp == SearchPolicy.trot) |
| ++step; |
| else |
| step <<= 1; |
| } |
| return below + this[below .. above].getTransitionIndex!( |
| SearchPolicy.binarySearch, test, V)(v); |
| } |
| |
| // lowerBound |
| /** |
| This function uses a search with policy $(D sp) to find the |
| largest left subrange on which $(D pred(x, value)) is $(D true) for |
| all $(D x) (e.g., if $(D pred) is "less than", returns the portion of |
| the range with elements strictly smaller than $(D value)). The search |
| schedule and its complexity are documented in |
| $(LREF SearchPolicy). See also STL's |
| $(HTTP sgi.com/tech/stl/lower_bound.html, lower_bound). |
| */ |
| auto lowerBound(SearchPolicy sp = SearchPolicy.binarySearch, V)(V value) |
| if (isTwoWayCompatible!(predFun, ElementType!Range, V) |
| && hasSlicing!Range) |
| { |
| return this[0 .. getTransitionIndex!(sp, geq)(value)]; |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| auto a = assumeSorted([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]); |
| auto p = a.lowerBound(4); |
| assert(equal(p, [ 0, 1, 2, 3 ])); |
| } |
| |
| // upperBound |
| /** |
| 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). |
| |
| For ranges that do not offer random access, $(D SearchPolicy.linear) |
| is the only policy allowed (and it must be specified explicitly lest it exposes |
| user code to unexpected inefficiencies). For random-access searches, all |
| policies are allowed, and $(D SearchPolicy.binarySearch) is the default. |
| |
| See_Also: STL's $(HTTP sgi.com/tech/stl/lower_bound.html,upper_bound). |
| */ |
| auto upperBound(SearchPolicy sp = SearchPolicy.binarySearch, V)(V value) |
| if (isTwoWayCompatible!(predFun, ElementType!Range, V)) |
| { |
| static assert(hasSlicing!Range || sp == SearchPolicy.linear, |
| "Specify SearchPolicy.linear explicitly for " |
| ~ typeof(this).stringof); |
| static if (sp == SearchPolicy.linear) |
| { |
| for (; !_input.empty && !predFun(value, _input.front); |
| _input.popFront()) |
| { |
| } |
| return this; |
| } |
| else |
| { |
| return this[getTransitionIndex!(sp, gt)(value) .. length]; |
| } |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| auto a = assumeSorted([ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]); |
| auto p = a.upperBound(3); |
| assert(equal(p, [4, 4, 5, 6])); |
| } |
| |
| |
| // equalRange |
| /** |
| Returns the subrange containing all elements $(D e) for which both $(D |
| pred(e, value)) and $(D pred(value, e)) evaluate to $(D false) (e.g., |
| if $(D pred) is "less than", returns the portion of the range with |
| elements equal to $(D value)). Uses a classic binary search with |
| interval halving until it finds a value that satisfies the condition, |
| then uses $(D SearchPolicy.gallopBackwards) to find the left boundary |
| and $(D SearchPolicy.gallop) to find the right boundary. These |
| policies are justified by the fact that the two boundaries are likely |
| to be near the first found value (i.e., equal ranges are relatively |
| small). Completes the entire search in $(BIGOH log(n)) time. See also |
| STL's $(HTTP sgi.com/tech/stl/equal_range.html, equal_range). |
| */ |
| auto equalRange(V)(V value) |
| if (isTwoWayCompatible!(predFun, ElementType!Range, V) |
| && isRandomAccessRange!Range) |
| { |
| size_t first = 0, count = _input.length; |
| while (count > 0) |
| { |
| immutable step = count / 2; |
| auto it = first + step; |
| if (predFun(_input[it], value)) |
| { |
| // Less than value, bump left bound up |
| first = it + 1; |
| count -= step + 1; |
| } |
| else if (predFun(value, _input[it])) |
| { |
| // Greater than value, chop count |
| count = step; |
| } |
| else |
| { |
| // Equal to value, do binary searches in the |
| // leftover portions |
| // Gallop towards the left end as it's likely nearby |
| immutable left = first |
| + this[first .. it] |
| .lowerBound!(SearchPolicy.gallopBackwards)(value).length; |
| first += count; |
| // Gallop towards the right end as it's likely nearby |
| immutable right = first |
| - this[it + 1 .. first] |
| .upperBound!(SearchPolicy.gallop)(value).length; |
| return this[left .. right]; |
| } |
| } |
| return this.init; |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| auto a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; |
| auto r = a.assumeSorted.equalRange(3); |
| assert(equal(r, [ 3, 3, 3 ])); |
| } |
| |
| // trisect |
| /** |
| Returns a tuple $(D r) such that $(D r[0]) is the same as the result |
| of $(D lowerBound(value)), $(D r[1]) is the same as the result of $(D |
| equalRange(value)), and $(D r[2]) is the same as the result of $(D |
| upperBound(value)). The call is faster than computing all three |
| separately. Uses a search schedule similar to $(D |
| equalRange). Completes the entire search in $(BIGOH log(n)) time. |
| */ |
| auto trisect(V)(V value) |
| if (isTwoWayCompatible!(predFun, ElementType!Range, V) |
| && isRandomAccessRange!Range && hasLength!Range) |
| { |
| import std.typecons : tuple; |
| size_t first = 0, count = _input.length; |
| while (count > 0) |
| { |
| immutable step = count / 2; |
| auto it = first + step; |
| if (predFun(_input[it], value)) |
| { |
| // Less than value, bump left bound up |
| first = it + 1; |
| count -= step + 1; |
| } |
| else if (predFun(value, _input[it])) |
| { |
| // Greater than value, chop count |
| count = step; |
| } |
| else |
| { |
| // Equal to value, do binary searches in the |
| // leftover portions |
| // Gallop towards the left end as it's likely nearby |
| immutable left = first |
| + this[first .. it] |
| .lowerBound!(SearchPolicy.gallopBackwards)(value).length; |
| first += count; |
| // Gallop towards the right end as it's likely nearby |
| immutable right = first |
| - this[it + 1 .. first] |
| .upperBound!(SearchPolicy.gallop)(value).length; |
| return tuple(this[0 .. left], this[left .. right], |
| this[right .. length]); |
| } |
| } |
| // No equal element was found |
| return tuple(this[0 .. first], this.init, this[first .. length]); |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| auto a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; |
| auto r = assumeSorted(a).trisect(3); |
| assert(equal(r[0], [ 1, 2 ])); |
| assert(equal(r[1], [ 3, 3, 3 ])); |
| assert(equal(r[2], [ 4, 4, 5, 6 ])); |
| } |
| |
| // contains |
| /** |
| Returns $(D true) if and only if $(D value) can be found in $(D |
| range), which is assumed to be sorted. Performs $(BIGOH log(r.length)) |
| evaluations of $(D pred). See also STL's $(HTTP |
| sgi.com/tech/stl/binary_search.html, binary_search). |
| */ |
| |
| bool contains(V)(V value) |
| if (isRandomAccessRange!Range) |
| { |
| if (empty) return false; |
| immutable i = getTransitionIndex!(SearchPolicy.binarySearch, geq)(value); |
| if (i >= length) return false; |
| return !predFun(value, _input[i]); |
| } |
| |
| // groupBy |
| /** |
| Returns a range of subranges of elements that are equivalent according to the |
| sorting relation. |
| */ |
| auto groupBy()() |
| { |
| import std.algorithm.iteration : chunkBy; |
| return _input.chunkBy!((a, b) => !predFun(a, b) && !predFun(b, a)); |
| } |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.sorting : sort; |
| 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. |
| |
| 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 sorted-ness, $(D SortedRange) will work erratically. |
| */ |
| @safe unittest |
| { |
| import std.algorithm.mutation : swap; |
| 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 |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| auto a = [ 10, 20, 30, 30, 30, 40, 40, 50, 60 ]; |
| auto r = assumeSorted(a).trisect(30); |
| assert(equal(r[0], [ 10, 20 ])); |
| assert(equal(r[1], [ 30, 30, 30 ])); |
| assert(equal(r[2], [ 40, 40, 50, 60 ])); |
| |
| r = assumeSorted(a).trisect(35); |
| assert(equal(r[0], [ 10, 20, 30, 30, 30 ])); |
| assert(r[1].empty); |
| assert(equal(r[2], [ 40, 40, 50, 60 ])); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| auto a = [ "A", "AG", "B", "E", "F" ]; |
| auto r = assumeSorted!"cmp(a,b) < 0"(a).trisect("B"w); |
| assert(equal(r[0], [ "A", "AG" ])); |
| assert(equal(r[1], [ "B" ])); |
| assert(equal(r[2], [ "E", "F" ])); |
| r = assumeSorted!"cmp(a,b) < 0"(a).trisect("A"d); |
| assert(r[0].empty); |
| assert(equal(r[1], [ "A" ])); |
| assert(equal(r[2], [ "AG", "B", "E", "F" ])); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| static void test(SearchPolicy pol)() |
| { |
| auto a = [ 1, 2, 3, 42, 52, 64 ]; |
| auto r = assumeSorted(a); |
| assert(equal(r.lowerBound(42), [1, 2, 3])); |
| |
| assert(equal(r.lowerBound!(pol)(42), [1, 2, 3])); |
| assert(equal(r.lowerBound!(pol)(41), [1, 2, 3])); |
| assert(equal(r.lowerBound!(pol)(43), [1, 2, 3, 42])); |
| assert(equal(r.lowerBound!(pol)(51), [1, 2, 3, 42])); |
| assert(equal(r.lowerBound!(pol)(3), [1, 2])); |
| assert(equal(r.lowerBound!(pol)(55), [1, 2, 3, 42, 52])); |
| assert(equal(r.lowerBound!(pol)(420), a)); |
| assert(equal(r.lowerBound!(pol)(0), a[0 .. 0])); |
| |
| assert(equal(r.upperBound!(pol)(42), [52, 64])); |
| assert(equal(r.upperBound!(pol)(41), [42, 52, 64])); |
| assert(equal(r.upperBound!(pol)(43), [52, 64])); |
| assert(equal(r.upperBound!(pol)(51), [52, 64])); |
| assert(equal(r.upperBound!(pol)(53), [64])); |
| assert(equal(r.upperBound!(pol)(55), [64])); |
| assert(equal(r.upperBound!(pol)(420), a[0 .. 0])); |
| assert(equal(r.upperBound!(pol)(0), a)); |
| } |
| |
| test!(SearchPolicy.trot)(); |
| test!(SearchPolicy.gallop)(); |
| test!(SearchPolicy.trotBackwards)(); |
| test!(SearchPolicy.gallopBackwards)(); |
| test!(SearchPolicy.binarySearch)(); |
| } |
| |
| @safe unittest |
| { |
| // Check for small arrays |
| int[] a; |
| auto r = assumeSorted(a); |
| a = [ 1 ]; |
| r = assumeSorted(a); |
| a = [ 1, 2 ]; |
| r = assumeSorted(a); |
| a = [ 1, 2, 3 ]; |
| r = assumeSorted(a); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.mutation : swap; |
| 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 |
| } |
| |
| @safe unittest |
| { |
| immutable(int)[] arr = [ 1, 2, 3 ]; |
| auto s = assumeSorted(arr); |
| } |
| |
| @system unittest |
| { |
| import std.algorithm.comparison : equal; |
| int[] arr = [100, 101, 102, 200, 201, 300]; |
| auto s = assumeSorted!((a, b) => a / 100 < b / 100)(arr); |
| assert(s.groupBy.equal!equal([[100, 101, 102], [200, 201], [300]])); |
| } |
| |
| // Test on an input range |
| @system unittest |
| { |
| import std.conv : text; |
| import std.file : exists, remove, tempDir; |
| import std.path : buildPath; |
| import std.stdio : File; |
| import std.uuid : randomUUID; |
| auto name = buildPath(tempDir(), "test.std.range.line-" ~ text(__LINE__) ~ |
| "." ~ randomUUID().toString()); |
| auto f = File(name, "w"); |
| scope(exit) if (exists(name)) remove(name); |
| // write a sorted range of lines to the file |
| f.write("abc\ndef\nghi\njkl"); |
| f.close(); |
| f.open(name, "r"); |
| auto r = assumeSorted(f.byLine()); |
| auto r1 = r.upperBound!(SearchPolicy.linear)("def"); |
| assert(r1.front == "ghi", r1.front); |
| f.close(); |
| } |
| |
| /** |
| Assumes $(D r) is sorted by predicate $(D pred) and returns the |
| corresponding $(D SortedRange!(pred, R)) having $(D r) as support. To |
| keep the checking costs low, the cost is $(BIGOH 1) in release mode |
| (no checks for sorted-ness are performed). In debug mode, a few random |
| elements of $(D r) are checked for sorted-ness. The size of the sample |
| is proportional $(BIGOH log(r.length)). That way, checking has no |
| effect on the complexity of subsequent operations specific to sorted |
| ranges (such as binary search). The probability of an arbitrary |
| unsorted range failing the test is very high (however, an |
| almost-sorted range is likely to pass it). To check for sorted-ness at |
| cost $(BIGOH n), use $(REF isSorted, std,algorithm,sorting). |
| */ |
| auto assumeSorted(alias pred = "a < b", R)(R r) |
| if (isInputRange!(Unqual!R)) |
| { |
| return SortedRange!(Unqual!R, pred)(r); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| static assert(isRandomAccessRange!(SortedRange!(int[]))); |
| int[] a = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]; |
| auto p = assumeSorted(a).lowerBound(4); |
| assert(equal(p, [0, 1, 2, 3])); |
| p = assumeSorted(a).lowerBound(5); |
| assert(equal(p, [0, 1, 2, 3, 4])); |
| p = assumeSorted(a).lowerBound(6); |
| assert(equal(p, [ 0, 1, 2, 3, 4, 5])); |
| p = assumeSorted(a).lowerBound(6.9); |
| assert(equal(p, [ 0, 1, 2, 3, 4, 5, 6])); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| int[] a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; |
| auto p = assumeSorted(a).upperBound(3); |
| assert(equal(p, [4, 4, 5, 6 ])); |
| p = assumeSorted(a).upperBound(4.2); |
| assert(equal(p, [ 5, 6 ])); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.conv : text; |
| |
| int[] a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; |
| auto p = assumeSorted(a).equalRange(3); |
| assert(equal(p, [ 3, 3, 3 ]), text(p)); |
| p = assumeSorted(a).equalRange(4); |
| assert(equal(p, [ 4, 4 ]), text(p)); |
| p = assumeSorted(a).equalRange(2); |
| assert(equal(p, [ 2 ])); |
| p = assumeSorted(a).equalRange(0); |
| assert(p.empty); |
| p = assumeSorted(a).equalRange(7); |
| assert(p.empty); |
| p = assumeSorted(a).equalRange(3.0); |
| assert(equal(p, [ 3, 3, 3])); |
| } |
| |
| @safe unittest |
| { |
| int[] a = [ 1, 2, 3, 3, 3, 4, 4, 5, 6 ]; |
| if (a.length) |
| { |
| auto b = a[a.length / 2]; |
| //auto r = sort(a); |
| //assert(r.contains(b)); |
| } |
| } |
| |
| @safe unittest |
| { |
| auto a = [ 5, 7, 34, 345, 677 ]; |
| auto r = assumeSorted(a); |
| a = null; |
| r = assumeSorted(a); |
| a = [ 1 ]; |
| r = assumeSorted(a); |
| } |
| |
| @system unittest |
| { |
| bool ok = true; |
| try |
| { |
| auto r2 = assumeSorted([ 677, 345, 34, 7, 5 ]); |
| debug ok = false; |
| } |
| catch (Throwable) |
| { |
| } |
| assert(ok); |
| } |
| |
| // issue 15003 |
| @nogc @safe unittest |
| { |
| static immutable a = [1, 2, 3, 4]; |
| auto r = a.assumeSorted; |
| } |
| |
| /++ |
| Wrapper which effectively makes it possible to pass a range by reference. |
| Both the original range and the RefRange will always have the exact same |
| elements. Any operation done on one will affect the other. So, for instance, |
| if it's passed to a function which would implicitly copy the original range |
| if it were passed to it, the original range is $(I not) copied but is |
| consumed as if it were a reference type. |
| |
| Note: |
| `save` works as normal and operates on a new _range, so if |
| `save` is ever called on the `RefRange`, then no operations on the |
| saved _range will affect the original. |
| |
| Params: |
| range = the range to construct the `RefRange` from |
| |
| Returns: |
| A `RefRange`. If the given _range is a class type |
| (and thus is already a reference type), then the original |
| range is returned rather than a `RefRange`. |
| +/ |
| struct RefRange(R) |
| if (isInputRange!R) |
| { |
| public: |
| |
| /++ +/ |
| this(R* range) @safe pure nothrow |
| { |
| _range = range; |
| } |
| |
| |
| /++ |
| This does not assign the pointer of $(D rhs) to this $(D RefRange). |
| Rather it assigns the range pointed to by $(D rhs) to the range pointed |
| to by this $(D RefRange). This is because $(I any) operation on a |
| $(D RefRange) is the same is if it occurred to the original range. The |
| one exception is when a $(D RefRange) is assigned $(D null) either |
| directly or because $(D rhs) is $(D null). In that case, $(D RefRange) |
| no longer refers to the original range but is $(D null). |
| +/ |
| auto opAssign(RefRange rhs) |
| { |
| if (_range && rhs._range) |
| *_range = *rhs._range; |
| else |
| _range = rhs._range; |
| |
| return this; |
| } |
| |
| /++ +/ |
| void opAssign(typeof(null) rhs) |
| { |
| _range = null; |
| } |
| |
| |
| /++ |
| A pointer to the wrapped range. |
| +/ |
| @property inout(R*) ptr() @safe inout pure nothrow |
| { |
| return _range; |
| } |
| |
| |
| version (StdDdoc) |
| { |
| /++ +/ |
| @property auto front() {assert(0);} |
| /++ Ditto +/ |
| @property auto front() const {assert(0);} |
| /++ Ditto +/ |
| @property auto front(ElementType!R value) {assert(0);} |
| } |
| else |
| { |
| @property auto front() |
| { |
| return (*_range).front; |
| } |
| |
| static if (is(typeof((*(cast(const R*)_range)).front))) @property auto front() const |
| { |
| return (*_range).front; |
| } |
| |
| static if (is(typeof((*_range).front = (*_range).front))) @property auto front(ElementType!R value) |
| { |
| return (*_range).front = value; |
| } |
| } |
| |
| |
| version (StdDdoc) |
| { |
| @property bool empty(); /// |
| @property bool empty() const; ///Ditto |
| } |
| else static if (isInfinite!R) |
| enum empty = false; |
| else |
| { |
| @property bool empty() |
| { |
| return (*_range).empty; |
| } |
| |
| static if (is(typeof((*cast(const R*)_range).empty))) @property bool empty() const |
| { |
| return (*_range).empty; |
| } |
| } |
| |
| |
| /++ +/ |
| void popFront() |
| { |
| return (*_range).popFront(); |
| } |
| |
| |
| version (StdDdoc) |
| { |
| /++ |
| Only defined if $(D isForwardRange!R) is $(D true). |
| +/ |
| @property auto save() {assert(0);} |
| /++ Ditto +/ |
| @property auto save() const {assert(0);} |
| /++ Ditto +/ |
| auto opSlice() {assert(0);} |
| /++ Ditto +/ |
| auto opSlice() const {assert(0);} |
| } |
| else static if (isForwardRange!R) |
| { |
| import std.traits : isSafe; |
| private alias S = typeof((*_range).save); |
| |
| static if (is(typeof((*cast(const R*)_range).save))) |
| private alias CS = typeof((*cast(const R*)_range).save); |
| |
| static if (isSafe!((R* r) => (*r).save)) |
| { |
| @property RefRange!S save() @trusted |
| { |
| mixin(_genSave()); |
| } |
| |
| static if (is(typeof((*cast(const R*)_range).save))) @property RefRange!CS save() @trusted const |
| { |
| mixin(_genSave()); |
| } |
| } |
| else |
| { |
| @property RefRange!S save() |
| { |
| mixin(_genSave()); |
| } |
| |
| static if (is(typeof((*cast(const R*)_range).save))) @property RefRange!CS save() const |
| { |
| mixin(_genSave()); |
| } |
| } |
| |
| auto opSlice()() |
| { |
| return save; |
| } |
| |
| auto opSlice()() const |
| { |
| return save; |
| } |
| |
| private static string _genSave() @safe pure nothrow |
| { |
| return `import std.conv : emplace;` ~ |
| `alias S = typeof((*_range).save);` ~ |
| `static assert(isForwardRange!S, S.stringof ~ " is not a forward range.");` ~ |
| `auto mem = new void[S.sizeof];` ~ |
| `emplace!S(mem, cast(S)(*_range).save);` ~ |
| `return RefRange!S(cast(S*) mem.ptr);`; |
| } |
| |
| static assert(isForwardRange!RefRange); |
| } |
| |
| |
| version (StdDdoc) |
| { |
| /++ |
| Only defined if $(D isBidirectionalRange!R) is $(D true). |
| +/ |
| @property auto back() {assert(0);} |
| /++ Ditto +/ |
| @property auto back() const {assert(0);} |
| /++ Ditto +/ |
| @property auto back(ElementType!R value) {assert(0);} |
| } |
| else static if (isBidirectionalRange!R) |
| { |
| @property auto back() |
| { |
| return (*_range).back; |
| } |
| |
| static if (is(typeof((*(cast(const R*)_range)).back))) @property auto back() const |
| { |
| return (*_range).back; |
| } |
| |
| static if (is(typeof((*_range).back = (*_range).back))) @property auto back(ElementType!R value) |
| { |
| return (*_range).back = value; |
| } |
| } |
| |
| |
| /++ Ditto +/ |
| static if (isBidirectionalRange!R) void popBack() |
| { |
| return (*_range).popBack(); |
| } |
| |
| |
| version (StdDdoc) |
| { |
| /++ |
| Only defined if $(D isRandomAccesRange!R) is $(D true). |
| +/ |
| auto ref opIndex(IndexType)(IndexType index) {assert(0);} |
| |
| /++ Ditto +/ |
| auto ref opIndex(IndexType)(IndexType index) const {assert(0);} |
| } |
| else static if (isRandomAccessRange!R) |
| { |
| auto ref opIndex(IndexType)(IndexType index) |
| if (is(typeof((*_range)[index]))) |
| { |
| return (*_range)[index]; |
| } |
| |
| auto ref opIndex(IndexType)(IndexType index) const |
| if (is(typeof((*cast(const R*)_range)[index]))) |
| { |
| return (*_range)[index]; |
| } |
| } |
| |
| |
| /++ |
| Only defined if $(D hasMobileElements!R) and $(D isForwardRange!R) are |
| $(D true). |
| +/ |
| static if (hasMobileElements!R && isForwardRange!R) auto moveFront() |
| { |
| return (*_range).moveFront(); |
| } |
| |
| |
| /++ |
| Only defined if $(D hasMobileElements!R) and $(D isBidirectionalRange!R) |
| are $(D true). |
| +/ |
| static if (hasMobileElements!R && isBidirectionalRange!R) auto moveBack() |
| { |
| return (*_range).moveBack(); |
| } |
| |
| |
| /++ |
| Only defined if $(D hasMobileElements!R) and $(D isRandomAccessRange!R) |
| are $(D true). |
| +/ |
| static if (hasMobileElements!R && isRandomAccessRange!R) auto moveAt(size_t index) |
| { |
| return (*_range).moveAt(index); |
| } |
| |
| |
| version (StdDdoc) |
| { |
| /++ |
| Only defined if $(D hasLength!R) is $(D true). |
| +/ |
| @property auto length() {assert(0);} |
| |
| /++ Ditto +/ |
| @property auto length() const {assert(0);} |
| |
| /++ Ditto +/ |
| alias opDollar = length; |
| } |
| else static if (hasLength!R) |
| { |
| @property auto length() |
| { |
| return (*_range).length; |
| } |
| |
| static if (is(typeof((*cast(const R*)_range).length))) @property auto length() const |
| { |
| return (*_range).length; |
| } |
| |
| alias opDollar = length; |
| } |
| |
| |
| version (StdDdoc) |
| { |
| /++ |
| Only defined if $(D hasSlicing!R) is $(D true). |
| +/ |
| auto opSlice(IndexType1, IndexType2) |
| (IndexType1 begin, IndexType2 end) {assert(0);} |
| |
| /++ Ditto +/ |
| auto opSlice(IndexType1, IndexType2) |
| (IndexType1 begin, IndexType2 end) const {assert(0);} |
| } |
| else static if (hasSlicing!R) |
| { |
| private alias T = typeof((*_range)[1 .. 2]); |
| static if (is(typeof((*cast(const R*)_range)[1 .. 2]))) |
| { |
| private alias CT = typeof((*cast(const R*)_range)[1 .. 2]); |
| } |
| |
| RefRange!T opSlice(IndexType1, IndexType2) |
| (IndexType1 begin, IndexType2 end) |
| if (is(typeof((*_range)[begin .. end]))) |
| { |
| mixin(_genOpSlice()); |
| } |
| |
| RefRange!CT opSlice(IndexType1, IndexType2) |
| (IndexType1 begin, IndexType2 end) const |
| if (is(typeof((*cast(const R*)_range)[begin .. end]))) |
| { |
| mixin(_genOpSlice()); |
| } |
| |
| private static string _genOpSlice() @safe pure nothrow |
| { |
| return `import std.conv : emplace;` ~ |
| `alias S = typeof((*_range)[begin .. end]);` ~ |
| `static assert(hasSlicing!S, S.stringof ~ " is not sliceable.");` ~ |
| `auto mem = new void[S.sizeof];` ~ |
| `emplace!S(mem, cast(S)(*_range)[begin .. end]);` ~ |
| `return RefRange!S(cast(S*) mem.ptr);`; |
| } |
| } |
| |
| |
| private: |
| |
| R* _range; |
| } |
| |
| /// Basic Example |
| @system unittest |
| { |
| import std.algorithm.searching : find; |
| ubyte[] buffer = [1, 9, 45, 12, 22]; |
| auto found1 = find(buffer, 45); |
| assert(found1 == [45, 12, 22]); |
| assert(buffer == [1, 9, 45, 12, 22]); |
| |
| auto wrapped1 = refRange(&buffer); |
| auto found2 = find(wrapped1, 45); |
| assert(*found2.ptr == [45, 12, 22]); |
| assert(buffer == [45, 12, 22]); |
| |
| auto found3 = find(wrapped1.save, 22); |
| assert(*found3.ptr == [22]); |
| assert(buffer == [45, 12, 22]); |
| |
| string str = "hello world"; |
| auto wrappedStr = refRange(&str); |
| assert(str.front == 'h'); |
| str.popFrontN(5); |
| assert(str == " world"); |
| assert(wrappedStr.front == ' '); |
| assert(*wrappedStr.ptr == " world"); |
| } |
| |
| /// opAssign Example. |
| @system unittest |
| { |
| ubyte[] buffer1 = [1, 2, 3, 4, 5]; |
| ubyte[] buffer2 = [6, 7, 8, 9, 10]; |
| auto wrapped1 = refRange(&buffer1); |
| auto wrapped2 = refRange(&buffer2); |
| assert(wrapped1.ptr is &buffer1); |
| assert(wrapped2.ptr is &buffer2); |
| assert(wrapped1.ptr !is wrapped2.ptr); |
| assert(buffer1 != buffer2); |
| |
| wrapped1 = wrapped2; |
| |
| //Everything points to the same stuff as before. |
| assert(wrapped1.ptr is &buffer1); |
| assert(wrapped2.ptr is &buffer2); |
| assert(wrapped1.ptr !is wrapped2.ptr); |
| |
| //But buffer1 has changed due to the assignment. |
| assert(buffer1 == [6, 7, 8, 9, 10]); |
| assert(buffer2 == [6, 7, 8, 9, 10]); |
| |
| buffer2 = [11, 12, 13, 14, 15]; |
| |
| //Everything points to the same stuff as before. |
| assert(wrapped1.ptr is &buffer1); |
| assert(wrapped2.ptr is &buffer2); |
| assert(wrapped1.ptr !is wrapped2.ptr); |
| |
| //But buffer2 has changed due to the assignment. |
| assert(buffer1 == [6, 7, 8, 9, 10]); |
| assert(buffer2 == [11, 12, 13, 14, 15]); |
| |
| wrapped2 = null; |
| |
| //The pointer changed for wrapped2 but not wrapped1. |
| assert(wrapped1.ptr is &buffer1); |
| assert(wrapped2.ptr is null); |
| assert(wrapped1.ptr !is wrapped2.ptr); |
| |
| //buffer2 is not affected by the assignment. |
| assert(buffer1 == [6, 7, 8, 9, 10]); |
| assert(buffer2 == [11, 12, 13, 14, 15]); |
| } |
| |
| @system unittest |
| { |
| import std.algorithm.iteration : filter; |
| { |
| ubyte[] buffer = [1, 2, 3, 4, 5]; |
| auto wrapper = refRange(&buffer); |
| auto p = wrapper.ptr; |
| auto f = wrapper.front; |
| wrapper.front = f; |
| auto e = wrapper.empty; |
| wrapper.popFront(); |
| auto s = wrapper.save; |
| auto b = wrapper.back; |
| wrapper.back = b; |
| wrapper.popBack(); |
| auto i = wrapper[0]; |
| wrapper.moveFront(); |
| wrapper.moveBack(); |
| wrapper.moveAt(0); |
| auto l = wrapper.length; |
| auto sl = wrapper[0 .. 1]; |
| assert(wrapper[0 .. $].length == buffer[0 .. $].length); |
| } |
| |
| { |
| ubyte[] buffer = [1, 2, 3, 4, 5]; |
| const wrapper = refRange(&buffer); |
| const p = wrapper.ptr; |
| const f = wrapper.front; |
| const e = wrapper.empty; |
| const s = wrapper.save; |
| const b = wrapper.back; |
| const i = wrapper[0]; |
| const l = wrapper.length; |
| const sl = wrapper[0 .. 1]; |
| } |
| |
| { |
| ubyte[] buffer = [1, 2, 3, 4, 5]; |
| auto filtered = filter!"true"(buffer); |
| auto wrapper = refRange(&filtered); |
| auto p = wrapper.ptr; |
| auto f = wrapper.front; |
| wrapper.front = f; |
| auto e = wrapper.empty; |
| wrapper.popFront(); |
| auto s = wrapper.save; |
| wrapper.moveFront(); |
| } |
| |
| { |
| ubyte[] buffer = [1, 2, 3, 4, 5]; |
| auto filtered = filter!"true"(buffer); |
| const wrapper = refRange(&filtered); |
| const p = wrapper.ptr; |
| |
| //Cannot currently be const. filter needs to be updated to handle const. |
| /+ |
| const f = wrapper.front; |
| const e = wrapper.empty; |
| const s = wrapper.save; |
| +/ |
| } |
| |
| { |
| string str = "hello world"; |
| auto wrapper = refRange(&str); |
| auto p = wrapper.ptr; |
| auto f = wrapper.front; |
| auto e = wrapper.empty; |
| wrapper.popFront(); |
| auto s = wrapper.save; |
| auto b = wrapper.back; |
| wrapper.popBack(); |
| } |
| |
| { |
| // Issue 16534 - opDollar should be defined if the |
| // wrapped range defines length. |
| auto range = 10.iota.takeExactly(5); |
| auto wrapper = refRange(&range); |
| assert(wrapper.length == 5); |
| assert(wrapper[0 .. $ - 1].length == 4); |
| } |
| } |
| |
| //Test assignment. |
| @system unittest |
| { |
| ubyte[] buffer1 = [1, 2, 3, 4, 5]; |
| ubyte[] buffer2 = [6, 7, 8, 9, 10]; |
| RefRange!(ubyte[]) wrapper1; |
| RefRange!(ubyte[]) wrapper2 = refRange(&buffer2); |
| assert(wrapper1.ptr is null); |
| assert(wrapper2.ptr is &buffer2); |
| |
| wrapper1 = refRange(&buffer1); |
| assert(wrapper1.ptr is &buffer1); |
| |
| wrapper1 = wrapper2; |
| assert(wrapper1.ptr is &buffer1); |
| assert(buffer1 == buffer2); |
| |
| wrapper1 = RefRange!(ubyte[]).init; |
| assert(wrapper1.ptr is null); |
| assert(wrapper2.ptr is &buffer2); |
| assert(buffer1 == buffer2); |
| assert(buffer1 == [6, 7, 8, 9, 10]); |
| |
| wrapper2 = null; |
| assert(wrapper2.ptr is null); |
| assert(buffer2 == [6, 7, 8, 9, 10]); |
| } |
| |
| @system unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.mutation : bringToFront; |
| import std.algorithm.searching : commonPrefix, find, until; |
| import std.algorithm.sorting : sort; |
| |
| //Test that ranges are properly consumed. |
| { |
| int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9]; |
| auto wrapper = refRange(&arr); |
| |
| assert(*find(wrapper, 41).ptr == [41, 3, 40, 4, 42, 9]); |
| assert(arr == [41, 3, 40, 4, 42, 9]); |
| |
| assert(*drop(wrapper, 2).ptr == [40, 4, 42, 9]); |
| assert(arr == [40, 4, 42, 9]); |
| |
| assert(equal(until(wrapper, 42), [40, 4])); |
| assert(arr == [42, 9]); |
| |
| assert(find(wrapper, 12).empty); |
| assert(arr.empty); |
| } |
| |
| { |
| string str = "Hello, world-like object."; |
| auto wrapper = refRange(&str); |
| |
| assert(*find(wrapper, "l").ptr == "llo, world-like object."); |
| assert(str == "llo, world-like object."); |
| |
| assert(equal(take(wrapper, 5), "llo, ")); |
| assert(str == "world-like object."); |
| } |
| |
| //Test that operating on saved ranges does not consume the original. |
| { |
| int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9]; |
| auto wrapper = refRange(&arr); |
| auto saved = wrapper.save; |
| saved.popFrontN(3); |
| assert(*saved.ptr == [41, 3, 40, 4, 42, 9]); |
| assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]); |
| } |
| |
| { |
| string str = "Hello, world-like object."; |
| auto wrapper = refRange(&str); |
| auto saved = wrapper.save; |
| saved.popFrontN(13); |
| assert(*saved.ptr == "like object."); |
| assert(str == "Hello, world-like object."); |
| } |
| |
| //Test that functions which use save work properly. |
| { |
| int[] arr = [1, 42]; |
| auto wrapper = refRange(&arr); |
| assert(equal(commonPrefix(wrapper, [1, 27]), [1])); |
| } |
| |
| { |
| int[] arr = [4, 5, 6, 7, 1, 2, 3]; |
| auto wrapper = refRange(&arr); |
| assert(bringToFront(wrapper[0 .. 4], wrapper[4 .. arr.length]) == 3); |
| assert(arr == [1, 2, 3, 4, 5, 6, 7]); |
| } |
| |
| //Test bidirectional functions. |
| { |
| int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9]; |
| auto wrapper = refRange(&arr); |
| |
| assert(wrapper.back == 9); |
| assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]); |
| |
| wrapper.popBack(); |
| assert(arr == [1, 42, 2, 41, 3, 40, 4, 42]); |
| } |
| |
| { |
| string str = "Hello, world-like object."; |
| auto wrapper = refRange(&str); |
| |
| assert(wrapper.back == '.'); |
| assert(str == "Hello, world-like object."); |
| |
| wrapper.popBack(); |
| assert(str == "Hello, world-like object"); |
| } |
| |
| //Test random access functions. |
| { |
| int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9]; |
| auto wrapper = refRange(&arr); |
| |
| assert(wrapper[2] == 2); |
| assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]); |
| |
| assert(*wrapper[3 .. 6].ptr != null, [41, 3, 40]); |
| assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]); |
| } |
| |
| //Test move functions. |
| { |
| int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9]; |
| auto wrapper = refRange(&arr); |
| |
| auto t1 = wrapper.moveFront(); |
| auto t2 = wrapper.moveBack(); |
| wrapper.front = t2; |
| wrapper.back = t1; |
| assert(arr == [9, 42, 2, 41, 3, 40, 4, 42, 1]); |
| |
| sort(wrapper.save); |
| assert(arr == [1, 2, 3, 4, 9, 40, 41, 42, 42]); |
| } |
| } |
| |
| @system unittest |
| { |
| struct S |
| { |
| @property int front() @safe const pure nothrow { return 0; } |
| enum bool empty = false; |
| void popFront() @safe pure nothrow { } |
| @property auto save() @safe pure nothrow { return this; } |
| } |
| |
| S s; |
| auto wrapper = refRange(&s); |
| static assert(isInfinite!(typeof(wrapper))); |
| } |
| |
| @system unittest |
| { |
| class C |
| { |
| @property int front() @safe const pure nothrow { return 0; } |
| @property bool empty() @safe const pure nothrow { return false; } |
| void popFront() @safe pure nothrow { } |
| @property auto save() @safe pure nothrow { return this; } |
| } |
| static assert(isForwardRange!C); |
| |
| auto c = new C; |
| auto cWrapper = refRange(&c); |
| static assert(is(typeof(cWrapper) == C)); |
| assert(cWrapper is c); |
| } |
| |
| @system unittest // issue 14373 |
| { |
| static struct R |
| { |
| @property int front() {return 0;} |
| void popFront() {empty = true;} |
| bool empty = false; |
| } |
| R r; |
| refRange(&r).popFront(); |
| assert(r.empty); |
| } |
| |
| @system unittest // issue 14575 |
| { |
| struct R |
| { |
| Object front; |
| alias back = front; |
| bool empty = false; |
| void popFront() {empty = true;} |
| alias popBack = popFront; |
| @property R save() {return this;} |
| } |
| static assert(isBidirectionalRange!R); |
| R r; |
| auto rr = refRange(&r); |
| |
| struct R2 |
| { |
| @property Object front() {return null;} |
| @property const(Object) front() const {return null;} |
| alias back = front; |
| bool empty = false; |
| void popFront() {empty = true;} |
| alias popBack = popFront; |
| @property R2 save() {return this;} |
| } |
| static assert(isBidirectionalRange!R2); |
| R2 r2; |
| auto rr2 = refRange(&r2); |
| } |
| |
| /// ditto |
| auto refRange(R)(R* range) |
| if (isInputRange!R) |
| { |
| static if (!is(R == class)) |
| return RefRange!R(range); |
| else |
| return *range; |
| } |
| |
| /*****************************************************************************/ |
| |
| @safe unittest // bug 9060 |
| { |
| import std.algorithm.iteration : map, joiner, group; |
| import std.algorithm.searching : until; |
| // fix for std.algorithm |
| auto r = map!(x => 0)([1]); |
| chain(r, r); |
| zip(r, r); |
| roundRobin(r, r); |
| |
| struct NRAR { |
| typeof(r) input; |
| @property empty() { return input.empty; } |
| @property front() { return input.front; } |
| void popFront() { input.popFront(); } |
| @property save() { return NRAR(input.save); } |
| } |
| auto n1 = NRAR(r); |
| cycle(n1); // non random access range version |
| |
| assumeSorted(r); |
| |
| // fix for std.range |
| joiner([r], [9]); |
| |
| struct NRAR2 { |
| NRAR input; |
| @property empty() { return true; } |
| @property front() { return input; } |
| void popFront() { } |
| @property save() { return NRAR2(input.save); } |
| } |
| auto n2 = NRAR2(n1); |
| joiner(n2); |
| |
| group(r); |
| |
| until(r, 7); |
| static void foo(R)(R r) { until!(x => x > 7)(r); } |
| foo(r); |
| } |
| |
| private struct Bitwise(R) |
| if (isInputRange!R && isIntegral!(ElementType!R)) |
| { |
| private: |
| alias ElemType = ElementType!R; |
| alias UnsignedElemType = Unsigned!ElemType; |
| |
| R parent; |
| enum bitsNum = ElemType.sizeof * 8; |
| size_t maskPos = 1; |
| |
| static if (isBidirectionalRange!R) |
| { |
| size_t backMaskPos = bitsNum; |
| } |
| |
| public: |
| this()(auto ref R range) |
| { |
| parent = range; |
| } |
| |
| static if (isInfinite!R) |
| { |
| enum empty = false; |
| } |
| else |
| { |
| /** |
| * Check if the range is empty |
| * |
| * Returns: a boolean true or false |
| */ |
| bool empty() |
| { |
| static if (hasLength!R) |
| { |
| return length == 0; |
| } |
| else static if (isBidirectionalRange!R) |
| { |
| if (parent.empty) |
| { |
| return true; |
| } |
| else |
| { |
| /* |
| If we have consumed the last element of the range both from |
| the front and the back, then the masks positions will overlap |
| */ |
| return parent.save.dropOne.empty && (maskPos > backMaskPos); |
| } |
| } |
| else |
| { |
| /* |
| If we consumed the last element of the range, but not all the |
| bits in the last element |
| */ |
| return parent.empty; |
| } |
| } |
| } |
| |
| bool front() |
| { |
| assert(!empty); |
| return (parent.front & mask(maskPos)) != 0; |
| } |
| |
| void popFront() |
| { |
| assert(!empty); |
| ++maskPos; |
| if (maskPos > bitsNum) |
| { |
| parent.popFront; |
| maskPos = 1; |
| } |
| } |
| |
| static if (hasLength!R) |
| { |
| size_t length() |
| { |
| auto len = parent.length * bitsNum - (maskPos - 1); |
| static if (isBidirectionalRange!R) |
| { |
| len -= bitsNum - backMaskPos; |
| } |
| return len; |
| } |
| |
| alias opDollar = length; |
| } |
| |
| static if (isForwardRange!R) |
| { |
| typeof(this) save() |
| { |
| auto result = this; |
| result.parent = parent.save; |
| return result; |
| } |
| } |
| |
| static if (isBidirectionalRange!R) |
| { |
| bool back() |
| { |
| assert(!empty); |
| return (parent.back & mask(backMaskPos)) != 0; |
| } |
| |
| void popBack() |
| { |
| assert(!empty); |
| --backMaskPos; |
| if (backMaskPos == 0) |
| { |
| parent.popBack; |
| backMaskPos = bitsNum; |
| } |
| } |
| } |
| |
| static if (isRandomAccessRange!R) |
| { |
| /** |
| Return the `n`th bit within the range |
| */ |
| bool opIndex(size_t n) |
| in |
| { |
| /* |
| If it does not have the length property, it means that R is |
| an infinite range |
| */ |
| static if (hasLength!R) |
| { |
| assert(n < length, "Index out of bounds"); |
| } |
| } |
| body |
| { |
| immutable size_t remainingBits = bitsNum - maskPos + 1; |
| // If n >= maskPos, then the bit sign will be 1, otherwise 0 |
| immutable sizediff_t sign = (remainingBits - n - 1) >> (sizediff_t.sizeof * 8 - 1); |
| /* |
| By truncating n with remainingBits bits we have skipped the |
| remaining bits in parent[0], so we need to add 1 to elemIndex. |
| |
| Because bitsNum is a power of 2, n / bitsNum == n >> bitsNum.bsf |
| */ |
| import core.bitop : bsf; |
| immutable size_t elemIndex = sign * (((n - remainingBits) >> bitsNum.bsf) + 1); |
| |
| /* |
| Since the indexing is from LSB to MSB, we need to index at the |
| remainder of (n - remainingBits). |
| |
| Because bitsNum is a power of 2, n % bitsNum == n & (bitsNum - 1) |
| */ |
| immutable size_t elemMaskPos = (sign ^ 1) * (maskPos + n) |
| + sign * (1 + ((n - remainingBits) & (bitsNum - 1))); |
| |
| return (parent[elemIndex] & mask(elemMaskPos)) != 0; |
| } |
| |
| static if (hasAssignableElements!R) |
| { |
| /** |
| Assigns `flag` to the `n`th bit within the range |
| */ |
| void opIndexAssign(bool flag, size_t n) |
| in |
| { |
| static if (hasLength!R) |
| { |
| assert(n < length, "Index out of bounds"); |
| } |
| } |
| body |
| { |
| import core.bitop : bsf; |
| |
| immutable size_t remainingBits = bitsNum - maskPos + 1; |
| immutable sizediff_t sign = (remainingBits - n - 1) >> (sizediff_t.sizeof * 8 - 1); |
| immutable size_t elemIndex = sign * (((n - remainingBits) >> bitsNum.bsf) + 1); |
| immutable size_t elemMaskPos = (sign ^ 1) * (maskPos + n) |
| + sign * (1 + ((n - remainingBits) & (bitsNum - 1))); |
| |
| auto elem = parent[elemIndex]; |
| auto elemMask = mask(elemMaskPos); |
| parent[elemIndex] = cast(UnsignedElemType)(flag * (elem | elemMask) |
| + (flag ^ 1) * (elem & ~elemMask)); |
| } |
| } |
| |
| Bitwise!R opSlice() |
| { |
| return this.save; |
| } |
| |
| Bitwise!R opSlice(size_t start, size_t end) |
| in |
| { |
| assert(start < end, "Invalid bounds: end <= start"); |
| } |
| body |
| { |
| import core.bitop : bsf; |
| |
| size_t remainingBits = bitsNum - maskPos + 1; |
| sizediff_t sign = (remainingBits - start - 1) >> (sizediff_t.sizeof * 8 - 1); |
| immutable size_t startElemIndex = sign * (((start - remainingBits) >> bitsNum.bsf) + 1); |
| immutable size_t startElemMaskPos = (sign ^ 1) * (maskPos + start) |
| + sign * (1 + ((start - remainingBits) & (bitsNum - 1))); |
| |
| immutable size_t sliceLen = end - start - 1; |
| remainingBits = bitsNum - startElemMaskPos + 1; |
| sign = (remainingBits - sliceLen - 1) >> (sizediff_t.sizeof * 8 - 1); |
| immutable size_t endElemIndex = startElemIndex |
| + sign * (((sliceLen - remainingBits) >> bitsNum.bsf) + 1); |
| immutable size_t endElemMaskPos = (sign ^ 1) * (startElemMaskPos + sliceLen) |
| + sign * (1 + ((sliceLen - remainingBits) & (bitsNum - 1))); |
| |
| typeof(return) result; |
| // Get the slice to be returned from the parent |
| result.parent = (parent[startElemIndex .. endElemIndex + 1]).save; |
| result.maskPos = startElemMaskPos; |
| static if (isBidirectionalRange!R) |
| { |
| result.backMaskPos = endElemMaskPos; |
| } |
| return result; |
| } |
| } |
| |
| private: |
| auto mask(size_t maskPos) |
| { |
| return (1UL << (maskPos - 1UL)); |
| } |
| } |
| |
| /** |
| Bitwise adapter over an integral type range. Consumes the range elements bit by |
| bit, from the least significant bit to the most significant bit. |
| |
| Params: |
| R = an integral input range to iterate over |
| range = range to consume bit by by |
| |
| Returns: |
| A `Bitwise` input range with propagated forward, bidirectional |
| and random access capabilities |
| */ |
| auto bitwise(R)(auto ref R range) |
| if (isInputRange!R && isIntegral!(ElementType!R)) |
| { |
| return Bitwise!R(range); |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.format : format; |
| |
| // 00000011 00001001 |
| ubyte[] arr = [3, 9]; |
| auto r = arr.bitwise; |
| |
| // iterate through it as with any other range |
| assert(format("%(%d%)", r) == "1100000010010000"); |
| assert(format("%(%d%)", r.retro).equal("1100000010010000".retro)); |
| |
| auto r2 = r[5 .. $]; |
| // set a bit |
| r[2] = 1; |
| assert(arr[0] == 7); |
| assert(r[5] == r2[0]); |
| } |
| |
| /// You can use bitwise to implement an uniform bool generator |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.random : rndGen; |
| |
| auto rb = rndGen.bitwise; |
| static assert(isInfinite!(typeof(rb))); |
| |
| auto rb2 = rndGen.bitwise; |
| // Don't forget that structs are passed by value |
| assert(rb.take(10).equal(rb2.take(10))); |
| } |
| |
| // Test nogc inference |
| @safe @nogc unittest |
| { |
| static ubyte[] arr = [3, 9]; |
| auto bw = arr.bitwise; |
| auto bw2 = bw[]; |
| auto bw3 = bw[8 .. $]; |
| bw3[2] = true; |
| |
| assert(arr[1] == 13); |
| assert(bw[$ - 6]); |
| assert(bw[$ - 6] == bw2[$ - 6]); |
| assert(bw[$ - 6] == bw3[$ - 6]); |
| } |
| |
| // Test all range types over all integral types |
| @safe pure nothrow unittest |
| { |
| import std.internal.test.dummyrange; |
| |
| alias IntegralTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, |
| long, ulong); |
| foreach (IntegralType; IntegralTypes) |
| { |
| foreach (T; AllDummyRangesType!(IntegralType[])) |
| { |
| T a; |
| auto bw = Bitwise!T(a); |
| |
| static if (isForwardRange!T) |
| { |
| auto bwFwdSave = bw.save; |
| } |
| |
| static if (isBidirectionalRange!T) |
| { |
| auto bwBack = bw.save; |
| auto bwBackSave = bw.save; |
| } |
| |
| static if (hasLength!T) |
| { |
| auto bwLength = bw.length; |
| assert(bw.length == (IntegralType.sizeof * 8 * a.length)); |
| static if (isForwardRange!T) |
| { |
| assert(bw.length == bwFwdSave.length); |
| } |
| } |
| |
| // Make sure front and back are not the mechanisms that modify the range |
| long numCalls = 42; |
| bool initialFrontValue; |
| |
| if (!bw.empty) |
| { |
| initialFrontValue = bw.front; |
| } |
| |
| while (!bw.empty && (--numCalls)) |
| { |
| bw.front; |
| assert(bw.front == initialFrontValue); |
| } |
| |
| /* |
| Check that empty works properly and that popFront does not get called |
| more times than it should |
| */ |
| numCalls = 0; |
| while (!bw.empty) |
| { |
| ++numCalls; |
| |
| static if (hasLength!T) |
| { |
| assert(bw.length == bwLength); |
| --bwLength; |
| } |
| |
| static if (isForwardRange!T) |
| { |
| assert(bw.front == bwFwdSave.front); |
| bwFwdSave.popFront(); |
| } |
| |
| static if (isBidirectionalRange!T) |
| { |
| assert(bwBack.front == bwBackSave.front); |
| bwBack.popBack(); |
| bwBackSave.popBack(); |
| } |
| bw.popFront(); |
| } |
| |
| auto rangeLen = numCalls / (IntegralType.sizeof * 8); |
| assert(numCalls == (IntegralType.sizeof * 8 * rangeLen)); |
| assert(bw.empty); |
| static if (isForwardRange!T) |
| { |
| assert(bwFwdSave.empty); |
| } |
| |
| static if (isBidirectionalRange!T) |
| { |
| assert(bwBack.empty); |
| } |
| } |
| } |
| } |
| |
| // Test opIndex and opSlice |
| @system unittest |
| { |
| alias IntegralTypes = AliasSeq!(byte, ubyte, short, ushort, int, uint, |
| long, ulong); |
| foreach (IntegralType; IntegralTypes) |
| { |
| size_t bitsNum = IntegralType.sizeof * 8; |
| |
| auto first = cast(IntegralType)(1); |
| |
| // 2 ^ (bitsNum - 1) |
| auto second = cast(IntegralType)(cast(IntegralType)(1) << (bitsNum - 2)); |
| |
| IntegralType[] a = [first, second]; |
| auto bw = Bitwise!(IntegralType[])(a); |
| |
| // Check against lsb of a[0] |
| assert(bw[0] == true); |
| // Check against msb - 1 of a[1] |
| assert(bw[2 * bitsNum - 2] == true); |
| |
| bw.popFront(); |
| assert(bw[2 * bitsNum - 3] == true); |
| |
| import core.exception : Error; |
| import std.exception : assertThrown; |
| |
| // Check out of bounds error |
| assertThrown!Error(bw[2 * bitsNum - 1]); |
| |
| bw[2] = true; |
| assert(bw[2] == true); |
| bw.popFront(); |
| assert(bw[1] == true); |
| |
| auto bw2 = bw[0 .. $ - 5]; |
| auto bw3 = bw2[]; |
| assert(bw2.length == (bw.length - 5)); |
| assert(bw2.length == bw3.length); |
| bw2.popFront(); |
| assert(bw2.length != bw3.length); |
| } |
| } |
| |
| /********************************* |
| * An OutputRange that discards the data it receives. |
| */ |
| struct NullSink |
| { |
| void put(E)(E){} |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.iteration : map; |
| import std.algorithm.mutation : copy; |
| [4, 5, 6].map!(x => x * 2).copy(NullSink()); // data is discarded |
| } |
| |
| |
| /++ |
| |
| Implements a "tee" style pipe, wrapping an input range so that elements of the |
| range can be passed to a provided function or $(LREF OutputRange) as they are |
| iterated over. This is useful for printing out intermediate values in a long |
| chain of range code, performing some operation with side-effects on each call |
| to $(D front) or $(D popFront), or diverting the elements of a range into an |
| auxiliary $(LREF OutputRange). |
| |
| It is important to note that as the resultant range is evaluated lazily, |
| in the case of the version of $(D tee) that takes a function, the function |
| will not actually be executed until the range is "walked" using functions |
| that evaluate ranges, such as $(REF array, std,array) or |
| $(REF fold, std,algorithm,iteration). |
| |
| Params: |
| pipeOnPop = If `Yes.pipeOnPop`, simply iterating the range without ever |
| calling `front` is enough to have `tee` mirror elements to `outputRange` (or, |
| respectively, `fun`). If `No.pipeOnPop`, only elements for which `front` does |
| get called will be also sent to `outputRange`/`fun`. |
| inputRange = The input range being passed through. |
| outputRange = This range will receive elements of `inputRange` progressively |
| as iteration proceeds. |
| fun = This function will be called with elements of `inputRange` |
| progressively as iteration proceeds. |
| |
| Returns: |
| An input range that offers the elements of `inputRange`. Regardless of |
| whether `inputRange` is a more powerful range (forward, bidirectional etc), |
| the result is always an input range. Reading this causes `inputRange` to be |
| iterated and returns its elements in turn. In addition, the same elements |
| will be passed to `outputRange` or `fun` as well. |
| |
| See_Also: $(REF each, std,algorithm,iteration) |
| +/ |
| auto tee(Flag!"pipeOnPop" pipeOnPop = Yes.pipeOnPop, R1, R2)(R1 inputRange, R2 outputRange) |
| if (isInputRange!R1 && isOutputRange!(R2, ElementType!R1)) |
| { |
| static struct Result |
| { |
| private R1 _input; |
| private R2 _output; |
| static if (!pipeOnPop) |
| { |
| private bool _frontAccessed; |
| } |
| |
| static if (hasLength!R1) |
| { |
| @property auto length() |
| { |
| return _input.length; |
| } |
| } |
| |
| static if (isInfinite!R1) |
| { |
| enum bool empty = false; |
| } |
| else |
| { |
| @property bool empty() { return _input.empty; } |
| } |
| |
| void popFront() |
| { |
| assert(!_input.empty, "Attempting to popFront an empty tee"); |
| static if (pipeOnPop) |
| { |
| put(_output, _input.front); |
| } |
| else |
| { |
| _frontAccessed = false; |
| } |
| _input.popFront(); |
| } |
| |
| @property auto ref front() |
| { |
| assert(!_input.empty, "Attempting to fetch the front of an empty tee"); |
| static if (!pipeOnPop) |
| { |
| if (!_frontAccessed) |
| { |
| _frontAccessed = true; |
| put(_output, _input.front); |
| } |
| } |
| return _input.front; |
| } |
| } |
| |
| return Result(inputRange, outputRange); |
| } |
| |
| /// Ditto |
| auto tee(alias fun, Flag!"pipeOnPop" pipeOnPop = Yes.pipeOnPop, R1)(R1 inputRange) |
| if (is(typeof(fun) == void) || isSomeFunction!fun) |
| { |
| import std.traits : isDelegate, isFunctionPointer; |
| /* |
| Distinguish between function literals and template lambdas |
| when using either as an $(LREF OutputRange). Since a template |
| has no type, typeof(template) will always return void. |
| If it's a template lambda, it's first necessary to instantiate |
| it with $(D ElementType!R1). |
| */ |
| static if (is(typeof(fun) == void)) |
| alias _fun = fun!(ElementType!R1); |
| else |
| alias _fun = fun; |
| |
| static if (isFunctionPointer!_fun || isDelegate!_fun) |
| { |
| return tee!pipeOnPop(inputRange, _fun); |
| } |
| else |
| { |
| return tee!pipeOnPop(inputRange, &_fun); |
| } |
| } |
| |
| /// |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : filter, map; |
| |
| // Sum values while copying |
| int[] values = [1, 4, 9, 16, 25]; |
| int sum = 0; |
| auto newValues = values.tee!(a => sum += a).array; |
| assert(equal(newValues, values)); |
| assert(sum == 1 + 4 + 9 + 16 + 25); |
| |
| // Count values that pass the first filter |
| int count = 0; |
| auto newValues4 = values.filter!(a => a < 10) |
| .tee!(a => count++) |
| .map!(a => a + 1) |
| .filter!(a => a < 10); |
| |
| //Fine, equal also evaluates any lazy ranges passed to it. |
| //count is not 3 until equal evaluates newValues4 |
| assert(equal(newValues4, [2, 5])); |
| assert(count == 3); |
| } |
| |
| // |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : filter, map; |
| |
| int[] values = [1, 4, 9, 16, 25]; |
| |
| int count = 0; |
| auto newValues = values.filter!(a => a < 10) |
| .tee!(a => count++, No.pipeOnPop) |
| .map!(a => a + 1) |
| .filter!(a => a < 10); |
| |
| auto val = newValues.front; |
| assert(count == 1); |
| //front is only evaluated once per element |
| val = newValues.front; |
| assert(count == 1); |
| |
| //popFront() called, fun will be called |
| //again on the next access to front |
| newValues.popFront(); |
| newValues.front; |
| assert(count == 2); |
| |
| int[] preMap = new int[](3), postMap = []; |
| auto mappedValues = values.filter!(a => a < 10) |
| //Note the two different ways of using tee |
| .tee(preMap) |
| .map!(a => a + 1) |
| .tee!(a => postMap ~= a) |
| .filter!(a => a < 10); |
| assert(equal(mappedValues, [2, 5])); |
| assert(equal(preMap, [1, 4, 9])); |
| assert(equal(postMap, [2, 5, 10])); |
| } |
| |
| // |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.algorithm.iteration : filter, map; |
| |
| char[] txt = "Line one, Line 2".dup; |
| |
| bool isVowel(dchar c) |
| { |
| import std.string : indexOf; |
| return "AaEeIiOoUu".indexOf(c) != -1; |
| } |
| |
| int vowelCount = 0; |
| int shiftedCount = 0; |
| auto removeVowels = txt.tee!(c => isVowel(c) ? vowelCount++ : 0) |
| .filter!(c => !isVowel(c)) |
| .map!(c => (c == ' ') ? c : c + 1) |
| .tee!(c => isVowel(c) ? shiftedCount++ : 0); |
| assert(equal(removeVowels, "Mo o- Mo 3")); |
| assert(vowelCount == 6); |
| assert(shiftedCount == 3); |
| } |
| |
| @safe unittest |
| { |
| // Manually stride to test different pipe behavior. |
| void testRange(Range)(Range r) |
| { |
| const int strideLen = 3; |
| int i = 0; |
| ElementType!Range elem1; |
| ElementType!Range elem2; |
| while (!r.empty) |
| { |
| if (i % strideLen == 0) |
| { |
| //Make sure front is only |
| //evaluated once per item |
| elem1 = r.front; |
| elem2 = r.front; |
| assert(elem1 == elem2); |
| } |
| r.popFront(); |
| i++; |
| } |
| } |
| |
| string txt = "abcdefghijklmnopqrstuvwxyz"; |
| |
| int popCount = 0; |
| auto pipeOnPop = txt.tee!(a => popCount++); |
| testRange(pipeOnPop); |
| assert(popCount == 26); |
| |
| int frontCount = 0; |
| auto pipeOnFront = txt.tee!(a => frontCount++, No.pipeOnPop); |
| testRange(pipeOnFront); |
| assert(frontCount == 9); |
| } |
| |
| @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.meta : AliasSeq; |
| |
| //Test diverting elements to an OutputRange |
| string txt = "abcdefghijklmnopqrstuvwxyz"; |
| |
| dchar[] asink1 = []; |
| auto fsink = (dchar c) { asink1 ~= c; }; |
| auto result1 = txt.tee(fsink).array; |
| assert(equal(txt, result1) && (equal(result1, asink1))); |
| |
| dchar[] _asink1 = []; |
| auto _result1 = txt.tee!((dchar c) { _asink1 ~= c; })().array; |
| assert(equal(txt, _result1) && (equal(_result1, _asink1))); |
| |
| dchar[] asink2 = new dchar[](txt.length); |
| void fsink2(dchar c) { static int i = 0; asink2[i] = c; i++; } |
| auto result2 = txt.tee(&fsink2).array; |
| assert(equal(txt, result2) && equal(result2, asink2)); |
| |
| dchar[] asink3 = new dchar[](txt.length); |
| auto result3 = txt.tee(asink3).array; |
| assert(equal(txt, result3) && equal(result3, asink3)); |
| |
| foreach (CharType; AliasSeq!(char, wchar, dchar)) |
| { |
| auto appSink = appender!(CharType[])(); |
| auto appResult = txt.tee(appSink).array; |
| assert(equal(txt, appResult) && equal(appResult, appSink.data)); |
| } |
| |
| foreach (StringType; AliasSeq!(string, wstring, dstring)) |
| { |
| auto appSink = appender!StringType(); |
| auto appResult = txt.tee(appSink).array; |
| assert(equal(txt, appResult) && equal(appResult, appSink.data)); |
| } |
| } |
| |
| @safe unittest |
| { |
| // Issue 13483 |
| static void func1(T)(T x) {} |
| void func2(int x) {} |
| |
| auto r = [1, 2, 3, 4].tee!func1.tee!func2; |
| } |
| |
| /** |
| Extends the length of the input range `r` by padding out the start of the |
| range with the element `e`. The element `e` must be of a common type with |
| the element type of the range `r` as defined by $(REF CommonType, std, traits). |
| If `n` is less than the length of of `r`, then `r` is returned unmodified. |
| |
| If `r` is a string with Unicode characters in it, `padLeft` follows D's rules |
| about length for strings, which is not the number of characters, or |
| graphemes, but instead the number of encoding units. If you want to treat each |
| grapheme as only one encoding unit long, then call |
| $(REF byGrapheme, std, uni) before calling this function. |
| |
| If `r` has a length, then this is $(BIGOH 1). Otherwise, it's $(BIGOH r.length). |
| |
| Params: |
| r = an input range with a length, or a forward range |
| e = element to pad the range with |
| n = the length to pad to |
| |
| Returns: |
| A range containing the elements of the original range with the extra padding |
| |
| See Also: |
| $(REF leftJustifier, std, string) |
| */ |
| auto padLeft(R, E)(R r, E e, size_t n) |
| if ( |
| ((isInputRange!R && hasLength!R) || isForwardRange!R) && |
| !is(CommonType!(ElementType!R, E) == void) |
| ) |
| { |
| static if (hasLength!R) |
| auto dataLength = r.length; |
| else |
| auto dataLength = r.save.walkLength(n); |
| |
| return e.repeat(n > dataLength ? n - dataLength : 0).chain(r); |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| assert([1, 2, 3, 4].padLeft(0, 6).equal([0, 0, 1, 2, 3, 4])); |
| assert([1, 2, 3, 4].padLeft(0, 3).equal([1, 2, 3, 4])); |
| |
| assert("abc".padLeft('_', 6).equal("___abc")); |
| } |
| |
| @safe pure nothrow unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.internal.test.dummyrange : DummyRange, Length, RangeType, ReturnBy; |
| import std.meta : AliasSeq; |
| |
| alias DummyRanges = AliasSeq!( |
| DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Input), |
| DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Forward), |
| DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Bidirectional), |
| DummyRange!(ReturnBy.Reference, Length.Yes, RangeType.Random), |
| DummyRange!(ReturnBy.Reference, Length.No, RangeType.Forward), |
| DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Input), |
| DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Forward), |
| DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Bidirectional), |
| DummyRange!(ReturnBy.Value, Length.Yes, RangeType.Random), |
| DummyRange!(ReturnBy.Value, Length.No, RangeType.Forward) |
| ); |
| |
| foreach (Range; DummyRanges) |
| { |
| Range r; |
| assert(r |
| .padLeft(0, 12) |
| .equal([0, 0, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U]) |
| ); |
| } |
| } |
| |
| // Test nogc inference |
| @safe @nogc pure unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| static immutable r1 = [1, 2, 3, 4]; |
| static immutable r2 = [0, 0, 1, 2, 3, 4]; |
| assert(r1.padLeft(0, 6).equal(r2)); |
| } |
| |
| /** |
| Extend the length of the input range `r` by padding out the end of the range |
| with the element `e`. The element `e` must be of a common type with the |
| element type of the range `r` as defined by $(REF CommonType, std, traits). |
| If `n` is less than the length of of `r`, then the contents of `r` are |
| returned. |
| |
| The range primitives that the resulting range provides depends whether or not `r` |
| provides them. Except the functions `back` and `popBack`, which also require |
| the range to have a length as well as `back` and `popBack` |
| |
| Params: |
| r = an input range with a length |
| e = element to pad the range with |
| n = the length to pad to |
| |
| Returns: |
| A range containing the elements of the original range with the extra padding |
| |
| See Also: |
| $(REF rightJustifier, std, string) |
| */ |
| auto padRight(R, E)(R r, E e, size_t n) |
| if ( |
| isInputRange!R && |
| !isInfinite!R && |
| !is(CommonType!(ElementType!R, E) == void)) |
| { |
| static struct Result |
| { |
| private: |
| R data; |
| E element; |
| size_t counter; |
| static if (isBidirectionalRange!R && hasLength!R) size_t backPosition; |
| size_t maxSize; |
| |
| public: |
| bool empty() @property |
| { |
| return data.empty && counter >= maxSize; |
| } |
| |
| auto front() @property |
| { |
| assert(!empty, "Attempting to fetch the front of an empty padRight"); |
| return data.empty ? element : data.front; |
| } |
| |
| void popFront() |
| { |
| assert(!empty, "Attempting to popFront an empty padRight"); |
| ++counter; |
| |
| if (!data.empty) |
| { |
| data.popFront; |
| } |
| } |
| |
| static if (hasLength!R) |
| { |
| size_t length() @property |
| { |
| import std.algorithm.comparison : max; |
| return max(data.length, maxSize); |
| } |
| } |
| |
| static if (isForwardRange!R) |
| { |
| auto save() @property |
| { |
| typeof(this) result = this; |
| data = data.save; |
| return result; |
| } |
| } |
| |
| static if (isBidirectionalRange!R && hasLength!R) |
| { |
| auto back() @property |
| { |
| assert(!empty, "Attempting to fetch the back of an empty padRight"); |
| return backPosition > data.length ? element : data.back; |
| } |
| |
| void popBack() |
| { |
| assert(!empty, "Attempting to popBack an empty padRight"); |
| if (backPosition > data.length) |
| { |
| --backPosition; |
| --maxSize; |
| } |
| else |
| { |
| data.popBack; |
| } |
| } |
| } |
| |
| static if (isRandomAccessRange!R && hasLength!R) |
| { |
| E opIndex(size_t index) |
| { |
| assert(index <= this.length, "Index out of bounds"); |
| return (index > data.length && index <= maxSize) ? element : |
| data[index]; |
| } |
| } |
| |
| static if (hasSlicing!R && hasLength!R) |
| { |
| auto opSlice(size_t a, size_t b) |
| { |
| assert( |
| a <= b, |
| "Attempting to slice a padRight with a larger first argument than the second." |
| ); |
| assert( |
| b <= length, |
| "Attempting to slice using an out of bounds index on a padRight" |
| ); |
| return Result((b <= data.length) ? data[a .. b] : data[a .. data.length], |
| element, b - a); |
| } |
| |
| alias opDollar = length; |
| } |
| |
| this(R r, E e, size_t max) |
| { |
| data = r; |
| element = e; |
| maxSize = max; |
| static if (isBidirectionalRange!R && hasLength!R) |
| backPosition = max; |
| } |
| |
| @disable this(); |
| } |
| |
| return Result(r, e, n); |
| } |
| |
| /// |
| @safe pure unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| assert([1, 2, 3, 4].padRight(0, 6).equal([1, 2, 3, 4, 0, 0])); |
| assert([1, 2, 3, 4].padRight(0, 4).equal([1, 2, 3, 4])); |
| |
| assert("abc".padRight('_', 6).equal("abc___")); |
| } |
| |
| pure @safe unittest |
| { |
| import std.algorithm.comparison : equal; |
| import std.internal.test.dummyrange : AllDummyRanges, ReferenceInputRange; |
| import std.meta : AliasSeq; |
| |
| auto string_input_range = new ReferenceInputRange!dchar(['a', 'b', 'c']); |
| dchar padding = '_'; |
| assert(string_input_range.padRight(padding, 6).equal("abc___")); |
| |
| foreach (RangeType; AllDummyRanges) |
| { |
| RangeType r1; |
| assert(r1 |
| .padRight(0, 12) |
| .equal([1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 0, 0]) |
| ); |
| |
| // test if Result properly uses random access ranges |
| static if (isRandomAccessRange!RangeType) |
| { |
| RangeType r3; |
| assert(r3.padRight(0, 12)[0] == 1); |
| assert(r3.padRight(0, 12)[2] == 3); |
| assert(r3.padRight(0, 12)[11] == 0); |
| } |
| |
| // test if Result properly uses slicing and opDollar |
| static if (hasSlicing!RangeType) |
| { |
| RangeType r4; |
| assert(r4 |
| .padRight(0, 12)[0 .. 3] |
| .equal([1, 2, 3]) |
| ); |
| assert(r4 |
| .padRight(0, 12)[2 .. $] |
| .equal([3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 0, 0]) |
| ); |
| assert(r4 |
| .padRight(0, 12)[0 .. $] |
| .equal([1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 9U, 10U, 0, 0]) |
| ); |
| } |
| } |
| } |
| |
| // Test nogc inference |
| @safe @nogc pure unittest |
| { |
| import std.algorithm.comparison : equal; |
| |
| static immutable r1 = [1, 2, 3, 4]; |
| static immutable r2 = [1, 2, 3, 4, 0, 0]; |
| assert(r1.padRight(0, 6).equal(r2)); |
| } |