840 lines
20 KiB
D
840 lines
20 KiB
D
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/**
|
|
* Iteration algorithms.
|
|
*
|
|
* These algorithms wrap other ranges and modify the way, how the original
|
|
* range is iterated, or the order in which its elements are accessed.
|
|
*
|
|
* All algorithms in this module are lazy, they request the next element of the
|
|
* original range on demand.
|
|
*
|
|
* Copyright: Eugene Wissner 2018-2020.
|
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
|
* Mozilla Public License, v. 2.0).
|
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
|
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/algorithm/iteration.d,
|
|
* tanya/algorithm/iteration.d)
|
|
*/
|
|
module tanya.algorithm.iteration;
|
|
|
|
import std.algorithm.comparison;
|
|
import tanya.memory.lifetime;
|
|
import tanya.meta.trait;
|
|
import tanya.meta.transform;
|
|
import tanya.range;
|
|
import tanya.typecons;
|
|
|
|
// These predicates are used to help preserve `const` and `inout` for
|
|
// ranges built on other ranges.
|
|
|
|
private enum hasInoutFront(T) = is(typeof((inout ref T a) => a.front));
|
|
private enum hasInoutBack(T) = is(typeof((inout ref T a) => a.back));
|
|
private enum hasInoutIndex(T) = is(typeof((inout ref T a, size_t i) => a[i]));
|
|
|
|
private enum hasConstEmpty(T) = is(typeof(((const T* a) => (*a).empty)(null)) : bool);
|
|
private enum hasConstLength(T) = is(typeof(((const T* a) => (*a).length)(null)) : size_t);
|
|
private enum hasConstSave(T) = is(typeof(((const T* a) => (*a).save())(null)) : T);
|
|
private enum hasConstSlice(T) = is(typeof(((const T* a) => (*a)[0 .. $])(null)) : T);
|
|
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
// Test the definitions.
|
|
static assert(hasInoutFront!string);
|
|
static assert(hasInoutBack!string);
|
|
static assert(hasInoutIndex!string);
|
|
static assert(hasConstEmpty!string);
|
|
static assert(hasConstLength!string);
|
|
static assert(hasConstSave!string);
|
|
static assert(hasConstSlice!string);
|
|
|
|
// Test that Take propagates const/inout correctly.
|
|
alias TakeString = Take!(string, false);
|
|
static assert(hasInoutFront!TakeString);
|
|
static assert(hasInoutBack!TakeString);
|
|
static assert(hasInoutIndex!TakeString);
|
|
static assert(hasConstEmpty!TakeString);
|
|
static assert(hasConstLength!TakeString);
|
|
static assert(hasConstSave!TakeString);
|
|
static assert(hasConstSlice!TakeString);
|
|
|
|
// Test that Retro propagates const/inout correctly.
|
|
alias RetroString = Retro!string;
|
|
static assert(hasInoutFront!RetroString);
|
|
static assert(hasInoutBack!RetroString);
|
|
static assert(hasInoutIndex!RetroString);
|
|
static assert(hasConstEmpty!RetroString);
|
|
static assert(hasConstLength!RetroString);
|
|
static assert(hasConstSave!RetroString);
|
|
static assert(hasConstSlice!RetroString);
|
|
}
|
|
|
|
private struct Take(R, bool exactly)
|
|
{
|
|
private R source;
|
|
size_t length_;
|
|
|
|
@disable this();
|
|
|
|
private this(R source, size_t length)
|
|
{
|
|
this.source = source;
|
|
static if (!exactly && hasLength!R)
|
|
{
|
|
this.length_ = min(source.length, length);
|
|
}
|
|
else
|
|
{
|
|
this.length_ = length;
|
|
}
|
|
}
|
|
|
|
mixin(`@property auto ref front() ` ~ (hasInoutFront!R ? `inout ` : ``) ~
|
|
`in (!empty)
|
|
{
|
|
return this.source.front;
|
|
}`);
|
|
|
|
void popFront()
|
|
in (!empty)
|
|
{
|
|
this.source.popFront();
|
|
--this.length_;
|
|
}
|
|
|
|
mixin(`@property bool empty() ` ~ (exactly || isInfinite!R || hasConstEmpty!R ? `const ` : ``) ~
|
|
`{
|
|
static if (exactly || isInfinite!R)
|
|
{
|
|
return length == 0;
|
|
}
|
|
else
|
|
{
|
|
return this.length_ == 0 || this.source.empty;
|
|
}
|
|
}`);
|
|
|
|
static if (exactly || hasLength!R)
|
|
{
|
|
@property size_t length() const
|
|
{
|
|
return this.length_;
|
|
}
|
|
}
|
|
|
|
static if (hasAssignableElements!R)
|
|
{
|
|
@property void front(ref ElementType!R value)
|
|
in (!empty)
|
|
{
|
|
this.source.front = value;
|
|
}
|
|
|
|
@property void front(ElementType!R value)
|
|
in (!empty)
|
|
{
|
|
this.source.front = move(value);
|
|
}
|
|
}
|
|
|
|
static if (isForwardRange!R)
|
|
{
|
|
mixin(`typeof(this) save() ` ~ (hasConstSave!R ? `const ` : ``) ~
|
|
`{
|
|
return typeof(this)(this.source.save(), length);
|
|
}`);
|
|
}
|
|
static if (isRandomAccessRange!R)
|
|
{
|
|
mixin(`@property auto ref back() ` ~ (hasInoutBack!R ? `inout ` : ``) ~
|
|
`in (!empty)
|
|
{
|
|
return this.source[this.length - 1];
|
|
}`);
|
|
|
|
void popBack()
|
|
in (!empty)
|
|
{
|
|
--this.length_;
|
|
}
|
|
|
|
mixin(`auto ref opIndex(size_t i) ` ~ (hasInoutIndex!R ? `inout ` : ``) ~
|
|
`in (i < length)
|
|
{
|
|
return this.source[i];
|
|
}`);
|
|
|
|
static if (hasAssignableElements!R)
|
|
{
|
|
@property void back(ref ElementType!R value)
|
|
in (!empty)
|
|
{
|
|
this.source[length - 1] = value;
|
|
}
|
|
|
|
@property void back(ElementType!R value)
|
|
in (!empty)
|
|
{
|
|
this.source[length - 1] = move(value);
|
|
}
|
|
|
|
void opIndexAssign(ref ElementType!R value, size_t i)
|
|
in (i < length)
|
|
{
|
|
this.source[i] = value;
|
|
}
|
|
|
|
void opIndexAssign(ElementType!R value, size_t i)
|
|
in (i < length)
|
|
{
|
|
this.source[i] = move(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
static if (!exactly && hasSlicing!R)
|
|
{
|
|
static if (is(typeof(length))) alias opDollar = length;
|
|
|
|
mixin(`auto opSlice(size_t i, size_t j) ` ~ (hasConstSlice!R ? `const ` : ``) ~
|
|
`in (i <= j)
|
|
in (j <= length)
|
|
{
|
|
return typeof(this)(this.source[i .. j], length);
|
|
}`);
|
|
}
|
|
|
|
version (unittest) static assert(isInputRange!Take);
|
|
}
|
|
|
|
/**
|
|
* Takes $(D_PARAM n) elements from $(D_PARAM range).
|
|
*
|
|
* If $(D_PARAM range) doesn't have $(D_PARAM n) elements, the resulting range
|
|
* spans all elements of $(D_PARAM range).
|
|
*
|
|
* $(D_PSYMBOL take) is particulary useful with infinite ranges. You can take
|
|
` $(B n) elements from such range and pass the result to an algorithm which
|
|
* expects a finit range.
|
|
*
|
|
* Params:
|
|
* R = Type of the adapted range.
|
|
* range = The range to take the elements from.
|
|
* n = The number of elements to take.
|
|
*
|
|
* Returns: A range containing maximum $(D_PARAM n) first elements of
|
|
* $(D_PARAM range).
|
|
*
|
|
* See_Also: $(D_PSYMBOL takeExactly).
|
|
*/
|
|
auto take(R)(R range, size_t n)
|
|
if (isInputRange!R)
|
|
{
|
|
static if (hasSlicing!R && hasLength!R)
|
|
{
|
|
if (range.length <= n)
|
|
return range;
|
|
else
|
|
return range[0 .. n];
|
|
}
|
|
// Special case: take(take(...), n)
|
|
else static if (is(Range == Take!(RRange, exact), RRange, bool exact))
|
|
{
|
|
if (n > range.length_)
|
|
n = range.length_;
|
|
static if (exact)
|
|
// `take(takeExactly(r, n0), n)` is rewritten `takeExactly(r, min(n0, n))`.
|
|
return Take!(RRange, true)(range.source, n);
|
|
else
|
|
// `take(take(r, n0), n)` is rewritten `take(r, min(n0, n))`.
|
|
return Take!(RRange, false)(range.source, n);
|
|
}
|
|
else static if (isInfinite!R)
|
|
{
|
|
// If the range is infinite then `take` is the same as `takeExactly`.
|
|
return Take!(R, true)(range, n);
|
|
}
|
|
else
|
|
{
|
|
return Take!(R, false)(range, n);
|
|
}
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
static struct InfiniteRange
|
|
{
|
|
private size_t front_ = 1;
|
|
|
|
enum bool empty = false;
|
|
|
|
@property size_t front() @nogc nothrow pure @safe
|
|
{
|
|
return this.front_;
|
|
}
|
|
|
|
@property void front(size_t i) @nogc nothrow pure @safe
|
|
{
|
|
this.front_ = i;
|
|
}
|
|
|
|
void popFront() @nogc nothrow pure @safe
|
|
{
|
|
++this.front_;
|
|
}
|
|
|
|
size_t opIndex(size_t i) @nogc nothrow pure @safe
|
|
{
|
|
return this.front_ + i;
|
|
}
|
|
|
|
void opIndexAssign(size_t value, size_t i) @nogc nothrow pure @safe
|
|
{
|
|
this.front = i + value;
|
|
}
|
|
|
|
InfiniteRange save() @nogc nothrow pure @safe
|
|
{
|
|
return this;
|
|
}
|
|
}
|
|
|
|
auto t = InfiniteRange().take(3);
|
|
assert(t.length == 3);
|
|
assert(t.front == 1);
|
|
assert(t.back == 3);
|
|
|
|
t.popFront();
|
|
assert(t.front == 2);
|
|
assert(t.back == 3);
|
|
|
|
t.popBack();
|
|
assert(t.front == 2);
|
|
assert(t.back == 2);
|
|
|
|
t.popFront();
|
|
assert(t.empty);
|
|
}
|
|
|
|
/**
|
|
* Takes exactly $(D_PARAM n) elements from $(D_PARAM range).
|
|
*
|
|
* $(D_PARAM range) must have at least $(D_PARAM n) elements.
|
|
*
|
|
* $(D_PSYMBOL takeExactly) is particulary useful with infinite ranges. You can
|
|
` take $(B n) elements from such range and pass the result to an algorithm
|
|
* which expects a finit range.
|
|
*
|
|
* Params:
|
|
* R = Type of the adapted range.
|
|
* range = The range to take the elements from.
|
|
* n = The number of elements to take.
|
|
*
|
|
* Returns: A range containing $(D_PARAM n) first elements of $(D_PARAM range).
|
|
*
|
|
* See_Also: $(D_PSYMBOL take).
|
|
*/
|
|
auto takeExactly(R)(R range, size_t n)
|
|
if (isInputRange!R)
|
|
{
|
|
static if (hasSlicing!R)
|
|
{
|
|
return range[0 .. n];
|
|
}
|
|
// Special case: takeExactly(take(range, ...), n) is takeExactly(range, n)
|
|
else static if (is(Range == Take!(RRange, exact), RRange, bool exact))
|
|
{
|
|
assert(n <= range.length_);
|
|
return Take!(RRange, true)(range.source, n);
|
|
}
|
|
else
|
|
{
|
|
return Take!(R, true)(range, n);
|
|
}
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
static struct InfiniteRange
|
|
{
|
|
private size_t front_ = 1;
|
|
|
|
enum bool empty = false;
|
|
|
|
@property size_t front() @nogc nothrow pure @safe
|
|
{
|
|
return this.front_;
|
|
}
|
|
|
|
@property void front(size_t i) @nogc nothrow pure @safe
|
|
{
|
|
this.front_ = i;
|
|
}
|
|
|
|
void popFront() @nogc nothrow pure @safe
|
|
{
|
|
++this.front_;
|
|
}
|
|
|
|
size_t opIndex(size_t i) @nogc nothrow pure @safe
|
|
{
|
|
return this.front_ + i;
|
|
}
|
|
|
|
void opIndexAssign(size_t value, size_t i) @nogc nothrow pure @safe
|
|
{
|
|
this.front = i + value;
|
|
}
|
|
|
|
InfiniteRange save() @nogc nothrow pure @safe
|
|
{
|
|
return this;
|
|
}
|
|
}
|
|
|
|
auto t = InfiniteRange().takeExactly(3);
|
|
assert(t.length == 3);
|
|
assert(t.front == 1);
|
|
assert(t.back == 3);
|
|
|
|
t.popFront();
|
|
assert(t.front == 2);
|
|
assert(t.back == 3);
|
|
|
|
t.popBack();
|
|
assert(t.front == 2);
|
|
assert(t.back == 2);
|
|
|
|
t.popFront();
|
|
assert(t.empty);
|
|
}
|
|
|
|
// Reverse-access-order range returned by `retro`.
|
|
private struct Retro(Range)
|
|
{
|
|
Range source;
|
|
|
|
@disable this();
|
|
|
|
private this(Range source) @safe
|
|
{
|
|
this.source = source;
|
|
}
|
|
|
|
mixin(`Retro save() ` ~ (hasConstSave!Range ? `const ` : ``) ~
|
|
`{
|
|
return Retro(source.save());
|
|
}`);
|
|
|
|
mixin(`@property auto ref front() ` ~ (hasInoutBack!Range ? `inout ` : ``) ~
|
|
`in (!empty)
|
|
{
|
|
return this.source.back;
|
|
}`);
|
|
|
|
void popFront()
|
|
in (!empty)
|
|
{
|
|
this.source.popBack();
|
|
}
|
|
|
|
mixin(`@property auto ref back() ` ~ (hasInoutFront!Range ? `inout ` : ``) ~
|
|
`in (!empty)
|
|
{
|
|
return this.source.front;
|
|
}`);
|
|
|
|
void popBack()
|
|
in (!empty)
|
|
{
|
|
this.source.popFront();
|
|
}
|
|
|
|
mixin(`@property bool empty() ` ~ (hasConstEmpty!Range ? `const ` : ``) ~
|
|
`{
|
|
return this.source.empty;
|
|
}`);
|
|
|
|
static if (hasLength!Range)
|
|
{
|
|
mixin(`@property size_t length() ` ~ (hasConstLength!Range ? `const ` : ``) ~
|
|
`{
|
|
return this.source.length;
|
|
}`);
|
|
}
|
|
|
|
static if (isRandomAccessRange!Range && hasLength!Range)
|
|
{
|
|
mixin(`auto ref opIndex(size_t i) ` ~ (hasInoutIndex!Range ? `inout ` : ``) ~
|
|
`in (i < length)
|
|
{
|
|
return this.source[$ - ++i];
|
|
}`);
|
|
}
|
|
|
|
static if (hasLength!Range && hasSlicing!Range)
|
|
{
|
|
alias opDollar = length;
|
|
|
|
mixin(`auto opSlice(size_t i, size_t j) ` ~ (hasConstSlice!Range ? `const ` : ``) ~
|
|
`in (i <= j)
|
|
in (j <= length)
|
|
{
|
|
return typeof(this)(this.source[$-j .. $-i]);
|
|
}`);
|
|
}
|
|
|
|
static if (hasAssignableElements!Range)
|
|
{
|
|
@property void front(ref ElementType!Range value)
|
|
in (!empty)
|
|
{
|
|
this.source.back = value;
|
|
}
|
|
|
|
@property void front(ElementType!Range value)
|
|
in (!empty)
|
|
{
|
|
this.source.back = move(value);
|
|
}
|
|
|
|
@property void back(ref ElementType!Range value)
|
|
in (!empty)
|
|
{
|
|
this.source.front = value;
|
|
}
|
|
|
|
@property void back(ElementType!Range value)
|
|
in (!empty)
|
|
{
|
|
this.source.front = move(value);
|
|
}
|
|
|
|
static if (isRandomAccessRange!Range && hasLength!Range)
|
|
{
|
|
void opIndexAssign(ref ElementType!Range value, size_t i)
|
|
in (i < length)
|
|
{
|
|
this.source[$ - ++i] = value;
|
|
}
|
|
|
|
void opIndexAssign(ElementType!Range value, size_t i)
|
|
in (i < length)
|
|
{
|
|
this.source[$ - ++i] = move(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
version (unittest) static assert(isBidirectionalRange!Retro);
|
|
}
|
|
|
|
/**
|
|
* Iterates a bidirectional range backwards.
|
|
*
|
|
* If $(D_PARAM Range) is a random-access range as well, the resulting range
|
|
* is a random-access range too.
|
|
*
|
|
* Params:
|
|
* Range = Bidirectional range type.
|
|
* range = Bidirectional range.
|
|
*
|
|
* Returns: Bidirectional range with the elements order reversed.
|
|
*/
|
|
auto retro(Range)(Range range)
|
|
if (isBidirectionalRange!Range)
|
|
{
|
|
// Special case: retro(retro(range)) is range
|
|
static if (is(Range == Retro!RRange, RRange))
|
|
return range.source;
|
|
else
|
|
return Retro!Range(range);
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
const int[3] given = [1, 2, 3];
|
|
const int[3] expected = [3, 2, 1];
|
|
|
|
auto actual = retro(given[]);
|
|
|
|
assert(actual.length == expected.length);
|
|
assert(!actual.empty);
|
|
assert(equal(actual, expected[]));
|
|
}
|
|
|
|
private struct SingletonByValue(E)
|
|
{
|
|
private Option!E element;
|
|
|
|
@disable this();
|
|
|
|
private this(U)(ref U element)
|
|
if (is(U == E))
|
|
{
|
|
this.element = move(element);
|
|
}
|
|
|
|
private this(U)(ref U element)
|
|
if (is(Unqual!U == Option!(Unqual!E)) || is(Unqual!U == Option!(const E)))
|
|
{
|
|
if (!element.isNothing)
|
|
{
|
|
this.element = element.get;
|
|
}
|
|
}
|
|
|
|
@property ref inout(E) front() inout
|
|
in (!empty)
|
|
{
|
|
return this.element.get;
|
|
}
|
|
|
|
alias back = front;
|
|
|
|
void popFront()
|
|
in (!empty)
|
|
{
|
|
this.element.reset();
|
|
}
|
|
|
|
alias popBack = popFront;
|
|
|
|
@property bool empty() const
|
|
{
|
|
return this.element.isNothing;
|
|
}
|
|
|
|
@property size_t length() const
|
|
{
|
|
return !this.element.isNothing;
|
|
}
|
|
|
|
auto save()
|
|
{
|
|
return SingletonByValue!E(this.element);
|
|
}
|
|
|
|
auto save() const
|
|
{
|
|
return SingletonByValue!(const E)(this.element);
|
|
}
|
|
|
|
ref inout(E) opIndex(size_t i) inout
|
|
in (!empty)
|
|
in (i == 0)
|
|
{
|
|
return this.element.get;
|
|
}
|
|
}
|
|
|
|
private struct SingletonByRef(E)
|
|
{
|
|
private E* element;
|
|
|
|
@disable this();
|
|
|
|
private this(return ref E element) @trusted
|
|
{
|
|
this.element = &element;
|
|
}
|
|
|
|
@property ref inout(E) front() inout return
|
|
in (!empty)
|
|
{
|
|
return *this.element;
|
|
}
|
|
|
|
alias back = front;
|
|
|
|
void popFront()
|
|
in (!empty)
|
|
{
|
|
this.element = null;
|
|
}
|
|
|
|
alias popBack = popFront;
|
|
|
|
@property bool empty() const
|
|
{
|
|
return this.element is null;
|
|
}
|
|
|
|
@property size_t length() const
|
|
{
|
|
return this.element !is null;
|
|
}
|
|
|
|
auto save() return
|
|
{
|
|
return typeof(this)(*this.element);
|
|
}
|
|
|
|
auto save() const return
|
|
{
|
|
return SingletonByRef!(const E)(*this.element);
|
|
}
|
|
|
|
ref inout(E) opIndex(size_t i) inout return
|
|
in (!empty)
|
|
in (i == 0)
|
|
{
|
|
return *this.element;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a bidirectional and random-access range with the single element
|
|
* $(D_PARAM element).
|
|
*
|
|
* If $(D_PARAM element) is passed by value the resulting range stores it
|
|
* internally. If $(D_PARAM element) is passed by reference, the resulting
|
|
* range keeps only a pointer to the element.
|
|
*
|
|
* Params:
|
|
* E = Element type.
|
|
* element = Element.
|
|
*
|
|
* Returns: A range with one element.
|
|
*/
|
|
auto singleton(E)(return E element)
|
|
if (isMutable!E)
|
|
{
|
|
return SingletonByValue!E(element);
|
|
}
|
|
|
|
/// ditto
|
|
auto singleton(E)(return ref E element)
|
|
{
|
|
return SingletonByRef!E(element);
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
auto singleChar = singleton('a');
|
|
|
|
assert(singleChar.length == 1);
|
|
assert(singleChar.front == 'a');
|
|
|
|
singleChar.popFront();
|
|
assert(singleChar.empty);
|
|
}
|
|
|
|
/**
|
|
* Accumulates all elements of a range using a function.
|
|
*
|
|
* $(D_PSYMBOL foldl) takes a function, an input range and the initial value.
|
|
* The function takes this initial value and the first element of the range (in
|
|
* this order), puts them together and returns the result. The return
|
|
* type of the function should be the same as the type of the initial value.
|
|
* This is than repeated for all the remaining elements of the range, whereby
|
|
* the value returned by the passed function is used at the place of the
|
|
* initial value.
|
|
*
|
|
* $(D_PSYMBOL foldl) accumulates from left to right.
|
|
*
|
|
* Params:
|
|
* F = Callable accepting the accumulator and a range element.
|
|
*/
|
|
template foldl(F...)
|
|
if (F.length == 1)
|
|
{
|
|
/**
|
|
* Params:
|
|
* R = Input range type.
|
|
* T = Type of the accumulated value.
|
|
* range = Input range.
|
|
* init = Initial value.
|
|
*
|
|
* Returns: Accumulated value.
|
|
*/
|
|
auto foldl(R, T)(R range, auto ref T init)
|
|
if (isInputRange!R && !isInfinite!R)
|
|
{
|
|
if (range.empty)
|
|
{
|
|
return init;
|
|
}
|
|
else
|
|
{
|
|
auto acc = F[0](init, getAndPopFront(range));
|
|
return foldl(range, acc);
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
int[3] range = [1, 2, 3];
|
|
const actual = foldl!((acc, x) => acc + x)(range[], 0);
|
|
|
|
assert(actual == 6);
|
|
}
|
|
|
|
/**
|
|
* Accumulates all elements of a range using a function.
|
|
*
|
|
* $(D_PSYMBOL foldr) takes a function, an input range and the initial value.
|
|
* The function takes this initial value and the first element of the range (in
|
|
* this order), puts them together and returns the result. The return
|
|
* type of the function should be the same as the type of the initial value.
|
|
* This is than repeated for all the remaining elements of the range, whereby
|
|
* the value returned by the passed function is used at the place of the
|
|
* initial value.
|
|
*
|
|
* $(D_PSYMBOL foldr) accumulates from right to left.
|
|
*
|
|
* Params:
|
|
* F = Callable accepting the accumulator and a range element.
|
|
*/
|
|
template foldr(F...)
|
|
if (F.length == 1)
|
|
{
|
|
/**
|
|
* Params:
|
|
* R = Bidirectional range type.
|
|
* T = Type of the accumulated value.
|
|
* range = Bidirectional range.
|
|
* init = Initial value.
|
|
*
|
|
* Returns: Accumulated value.
|
|
*/
|
|
auto foldr(R, T)(R range, auto ref T init)
|
|
if (isBidirectionalRange!R)
|
|
{
|
|
if (range.empty)
|
|
{
|
|
return init;
|
|
}
|
|
else
|
|
{
|
|
auto acc = F[0](init, getAndPopBack(range));
|
|
return foldr(range, acc);
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
@nogc nothrow pure @safe unittest
|
|
{
|
|
int[3] range = [1, 2, 3];
|
|
int[3] output;
|
|
const int[3] expected = [3, 2, 1];
|
|
|
|
alias f = (acc, x) {
|
|
acc.front = x;
|
|
acc.popFront;
|
|
return acc;
|
|
};
|
|
const actual = foldr!f(range[], output[]);
|
|
|
|
assert(output[] == expected[]);
|
|
}
|