| // 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)); | |
| } |