Permalink
Find file Copy path
6648 lines (5730 sloc) 193 KB
// Written in the D programming language.
/**
This is a submodule of $(MREF std, algorithm).
It contains generic iteration algorithms.
$(SCRIPT inhibitQuickIndex = 1;)
$(BOOKTABLE Cheat Sheet,
$(TR $(TH Function Name) $(TH Description))
$(T2 cache,
Eagerly evaluates and caches another range's `front`.)
$(T2 cacheBidirectional,
As above, but also provides `back` and `popBack`.)
$(T2 chunkBy,
`chunkBy!((a,b) => a[1] == b[1])([[1, 1], [1, 2], [2, 2], [2, 1]])`
returns a range containing 3 subranges: the first with just
`[1, 1]`; the second with the elements `[1, 2]` and `[2, 2]`;
and the third with just `[2, 1]`.)
$(T2 cumulativeFold,
`cumulativeFold!((a, b) => a + b)([1, 2, 3, 4])` returns a
lazily-evaluated range containing the successive reduced values `1`,
`3`, `6`, `10`.)
$(T2 each,
`each!writeln([1, 2, 3])` eagerly prints the numbers `1`, `2`
and `3` on their own lines.)
$(T2 filter,
`filter!(a => a > 0)([1, -1, 2, 0, -3])` iterates over elements `1`
and `2`.)
$(T2 filterBidirectional,
Similar to `filter`, but also provides `back` and `popBack` at
a small increase in cost.)
$(T2 fold,
`fold!((a, b) => a + b)([1, 2, 3, 4])` returns `10`.)
$(T2 group,
`group([5, 2, 2, 3, 3])` returns a range containing the tuples
`tuple(5, 1)`, `tuple(2, 2)`, and `tuple(3, 2)`.)
$(T2 joiner,
`joiner(["hello", "world!"], "; ")` returns a range that iterates
over the characters `"hello; world!"`. No new string is created -
the existing inputs are iterated.)
$(T2 map,
`map!(a => a * 2)([1, 2, 3])` lazily returns a range with the numbers
`2`, `4`, `6`.)
$(T2 mean,
Colloquially known as the average, `mean([1, 2, 3])` returns `2`.)
$(T2 permutations,
Lazily computes all permutations using Heap's algorithm.)
$(T2 reduce,
`reduce!((a, b) => a + b)([1, 2, 3, 4])` returns `10`.
This is the old implementation of `fold`.)
$(T2 splitter,
Lazily splits a range by a separator.)
$(T2 substitute,
`[1, 2].substitute(1, 0.1)` returns `[0.1, 2]`.)
$(T2 sum,
Same as `fold`, but specialized for accurate summation.)
$(T2 uniq,
Iterates over the unique elements in a range, which is assumed sorted.)
)
Copyright: Andrei Alexandrescu 2008-.
License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
Authors: $(HTTP erdani.com, Andrei Alexandrescu)
Source: $(PHOBOSSRC std/algorithm/iteration.d)
Macros:
T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
*/
module std.algorithm.iteration;
// FIXME
import std.functional; // : unaryFun, binaryFun;
import std.range.primitives;
import std.traits;
import std.typecons : Flag;
private template aggregate(fun...)
if (fun.length >= 1)
{
/* --Intentionally not ddoc--
* Aggregates elements in each subrange of the given range of ranges using
* the given aggregating function(s).
* Params:
* fun = One or more aggregating functions (binary functions that return a
* single _aggregate value of their arguments).
* ror = A range of ranges to be aggregated.
*
* Returns:
* A range representing the aggregated value(s) of each subrange
* of the original range. If only one aggregating function is specified,
* each element will be the aggregated value itself; if multiple functions
* are specified, each element will be a tuple of the aggregated values of
* each respective function.
*/
auto aggregate(RoR)(RoR ror)
if (isInputRange!RoR && isIterable!(ElementType!RoR))
{
return ror.map!(reduce!fun);
}
@safe unittest
{
import std.algorithm.comparison : equal, max, min;
auto data = [[4, 2, 1, 3], [4, 9, -1, 3, 2], [3]];
// Single aggregating function
auto agg1 = data.aggregate!max;
assert(agg1.equal([4, 9, 3]));
// Multiple aggregating functions
import std.typecons : tuple;
auto agg2 = data.aggregate!(max, min);
assert(agg2.equal([
tuple(4, 1),
tuple(9, -1),
tuple(3, 3)
]));
}
}
/++
`cache` eagerly evaluates $(REF_ALTTEXT front, front, std,range,primitives) of `range`
on each construction or call to $(REF_ALTTEXT popFront, popFront, std,range,primitives),
to store the result in a _cache.
The result is then directly returned when $(REF_ALTTEXT front, front, std,range,primitives) is called,
rather than re-evaluated.
This can be a useful function to place in a chain, after functions
that have expensive evaluation, as a lazy alternative to $(REF array, std,array).
In particular, it can be placed after a call to $(LREF map), or before a call
$(REF filter, std,range) or $(REF tee, std,range)
`cache` may provide
$(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives)
iteration if needed, but since this comes at an increased cost, it must be explicitly requested via the
call to `cacheBidirectional`. Furthermore, a bidirectional _cache will
evaluate the "center" element twice, when there is only one element left in
the range.
`cache` does not provide random access primitives,
as `cache` would be unable to _cache the random accesses.
If `Range` provides slicing primitives,
then `cache` will provide the same slicing primitives,
but `hasSlicing!Cache` will not yield true (as the $(REF hasSlicing, std,range,primitives)
trait also checks for random access).
Params:
range = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
Returns:
An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) with the cached values of range
+/
auto cache(Range)(Range range)
if (isInputRange!Range)
{
return _Cache!(Range, false)(range);
}
/// ditto
auto cacheBidirectional(Range)(Range range)
if (isBidirectionalRange!Range)
{
return _Cache!(Range, true)(range);
}
///
@safe unittest
{
import std.algorithm.comparison : equal;
import std.range, std.stdio;
import std.typecons : tuple;
ulong counter = 0;
double fun(int x)
{
++counter;
// http://en.wikipedia.org/wiki/Quartic_function
return ( (x + 4.0) * (x + 1.0) * (x - 1.0) * (x - 3.0) ) / 14.0 + 0.5;
}
// Without cache, with array (greedy)
auto result1 = iota(-4, 5).map!(a =>tuple(a, fun(a)))()
.filter!(a => a[1] < 0)()
.map!(a => a[0])()
.array();
// the values of x that have a negative y are:
assert(equal(result1, [-3, -2, 2]));
// Check how many times fun was evaluated.
// As many times as the number of items in both source and result.
assert(counter == iota(-4, 5).length + result1.length);
counter = 0;
// Without array, with cache (lazy)
auto result2 = iota(-4, 5).map!(a =>tuple(a, fun(a)))()
.cache()
.filter!(a => a[1] < 0)()
.map!(a => a[0])();
// the values of x that have a negative y are:
assert(equal(result2, [-3, -2, 2]));
// Check how many times fun was evaluated.
// Only as many times as the number of items in source.
assert(counter == iota(-4, 5).length);
}
/++
Tip: `cache` is eager when evaluating elements. If calling front on the
underlying range has a side effect, it will be observable before calling
front on the actual cached range.
Furthermore, care should be taken composing `cache` with $(REF take, std,range).
By placing `take` before `cache`, then `cache` will be "aware"
of when the range ends, and correctly stop caching elements when needed.
If calling front has no side effect though, placing `take` after `cache`
may yield a faster range.
Either way, the resulting ranges will be equivalent, but maybe not at the
same cost or side effects.
+/
@safe unittest
{
import std.algorithm.comparison : equal;
import std.range;
int i = 0;
auto r = iota(0, 4).tee!((a){i = a;}, No.pipeOnPop);
auto r1 = r.take(3).cache();
auto r2 = r.cache().take(3);
assert(equal(r1, [0, 1, 2]));
assert(i == 2); //The last "seen" element was 2. The data in cache has been cleared.
assert(equal(r2, [0, 1, 2]));
assert(i == 3); //cache has accessed 3. It is still stored internally by cache.
}
@safe unittest
{
import std.algorithm.comparison : equal;
import std.range;
auto a = [1, 2, 3, 4];
assert(equal(a.map!(a => (a - 1) * a)().cache(), [ 0, 2, 6, 12]));
assert(equal(a.map!(a => (a - 1) * a)().cacheBidirectional().retro(), [12, 6, 2, 0]));
auto r1 = [1, 2, 3, 4].cache() [1 .. $];
auto r2 = [1, 2, 3, 4].cacheBidirectional()[1 .. $];
assert(equal(r1, [2, 3, 4]));
assert(equal(r2, [2, 3, 4]));
}
@safe unittest
{
import std.algorithm.comparison : equal;
//immutable test
static struct S
{
int i;
this(int i)
{
//this.i = i;
}
}
immutable(S)[] s = [S(1), S(2), S(3)];
assert(equal(s.cache(), s));
assert(equal(s.cacheBidirectional(), s));
}
@safe pure nothrow unittest
{
import std.algorithm.comparison : equal;
//safety etc
auto a = [1, 2, 3, 4];
assert(equal(a.cache(), a));
assert(equal(a.cacheBidirectional(), a));
}
@safe unittest
{
char[][] stringbufs = ["hello".dup, "world".dup];
auto strings = stringbufs.map!((a)=>a.idup)().cache();
assert(strings.front is strings.front);
}
@safe unittest
{
import std.range : cycle;
import std.algorithm.comparison : equal;
auto c = [1, 2, 3].cycle().cache();
c = c[1 .. $];
auto d = c[0 .. 1];
assert(d.equal([2]));
}
@safe unittest
{
static struct Range
{
bool initialized = false;
bool front() @property {return initialized = true;}
void popFront() {initialized = false;}
enum empty = false;
}
auto r = Range().cache();
assert(r.source.initialized == true);
}
private struct _Cache(R, bool bidir)
{
import core.exception : RangeError;
private
{
import std.algorithm.internal : algoFormat;
import std.meta : AliasSeq;
alias E = ElementType!R;
alias UE = Unqual!E;
R source;
static if (bidir) alias CacheTypes = AliasSeq!(UE, UE);
else alias CacheTypes = AliasSeq!UE;
CacheTypes caches;
static assert(isAssignable!(UE, E) && is(UE : E),
algoFormat(
"Cannot instantiate range with %s because %s elements are not assignable to %s.",
R.stringof,
E.stringof,
UE.stringof
)
);
}
this(R range)
{
source = range;
if (!range.empty)
{
caches[0] = source.front;
static if (bidir)
caches[1] = source.back;
}
}
static if (isInfinite!R)
enum empty = false;
else
bool empty() @property
{
return source.empty;
}
static if (hasLength!R) auto length() @property
{
return source.length;
}
E front() @property
{
version (assert) if (empty) throw new RangeError();
return caches[0];
}
static if (bidir) E back() @property
{
version (assert) if (empty) throw new RangeError();
return caches[1];
}
void popFront()
{
version (assert) if (empty) throw new RangeError();
source.popFront();
if (!source.empty)
caches[0] = source.front;
else
caches = CacheTypes.init;
}
static if (bidir) void popBack()
{
version (assert) if (empty) throw new RangeError();
source.popBack();
if (!source.empty)
caches[1] = source.back;
else
caches = CacheTypes.init;
}
static if (isForwardRange!R)
{
private this(R source, ref CacheTypes caches)
{
this.source = source;
this.caches = caches;
}
typeof(this) save() @property
{
return typeof(this)(source.save, caches);
}
}
static if (hasSlicing!R)
{
enum hasEndSlicing = is(typeof(source[size_t.max .. $]));
static if (hasEndSlicing)
{
private static struct DollarToken{}
enum opDollar = DollarToken.init;
auto opSlice(size_t low, DollarToken)
{
return typeof(this)(source[low .. $]);
}
}
static if (!isInfinite!R)
{
typeof(this) opSlice(size_t low, size_t high)
{
return typeof(this)(source[low .. high]);
}
}
else static if (hasEndSlicing)
{
auto opSlice(size_t low, size_t high)
in
{
assert(low <= high, "Bounds error when slicing cache.");
}
do
{
import std.range : takeExactly;
return this[low .. $].takeExactly(high - low);
}
}
}
}
/**
Implements the homonym function (also known as `transform`) present
in many languages of functional flavor. The call `map!(fun)(range)`
returns a range of which elements are obtained by applying `fun(a)`
left to right for all elements `a` in `range`. The original ranges are
not changed. Evaluation is done lazily.
Params:
fun = one or more transformation functions
See_Also:
$(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function))
*/
template map(fun...)
if (fun.length >= 1)
{
/**
Params:
r = an $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
Returns:
A range with each fun applied to all the elements. If there is more than one
fun, the element type will be `Tuple` containing one element for each fun.
*/
auto map(Range)(Range r) if (isInputRange!(Unqual!Range))
{
import std.meta : AliasSeq, staticMap;
alias RE = ElementType!(Range);
static if (fun.length > 1)
{
import std.functional : adjoin;
import std.meta : staticIndexOf;
alias _funs = staticMap!(unaryFun, fun);
alias _fun = adjoin!_funs;
// Once DMD issue #5710 is fixed, this validation loop can be moved into a template.
foreach (f; _funs)
{
static assert(!is(typeof(f(RE.init)) == void),
"Mapping function(s) must not return void: " ~ _funs.stringof);
}
}
else
{
alias _fun = unaryFun!fun;
alias _funs = AliasSeq!(_fun);
// Do the validation separately for single parameters due to DMD issue #15777.
static assert(!is(typeof(_fun(RE.init)) == void),
"Mapping function(s) must not return void: " ~ _funs.stringof);
}
return MapResult!(_fun, Range)(r);
}
}
///
@safe unittest
{
import std.algorithm.comparison : equal;
import std.range : chain;
int[] arr1 = [ 1, 2, 3, 4 ];
int[] arr2 = [ 5, 6 ];
auto squares = map!(a => a * a)(chain(arr1, arr2));
assert(equal(squares, [ 1, 4, 9, 16, 25, 36 ]));
}
/**
Multiple functions can be passed to `map`. In that case, the
element type of `map` is a tuple containing one element for each
function.
*/
@safe unittest
{
auto sums = [2, 4, 6, 8];
auto products = [1, 4, 9, 16];
size_t i = 0;
foreach (result; [ 1, 2, 3, 4 ].map!("a + a", "a * a"))
{
assert(result[0] == sums[i]);
assert(result[1] == products[i]);
++i;
}
}
/**
You may alias `map` with some function(s) to a symbol and use
it separately:
*/
@safe unittest
{
import std.algorithm.comparison : equal;
import std.conv : to;
alias stringize = map!(to!string);
assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ]));
}
@safe unittest
{
// Verify workaround for DMD #15777
import std.algorithm.mutation, std.string;
auto foo(string[] args)
{
return args.map!strip;
}
}
private struct MapResult(alias fun, Range)
{
alias R = Unqual!Range;
R _input;
static if (isBidirectionalRange!R)
{
@property auto ref back()()
{
assert(!empty, "Attempting to fetch the back of an empty map.");
return fun(_input.back);
}
void popBack()()
{
assert(!empty, "Attempting to popBack an empty map.");
_input.popBack();
}
}
this(R input)
{
_input = input;
}
static if (isInfinite!R)
{
// Propagate infinite-ness.
enum bool empty = false;
}
else
{
@property bool empty()
{
return _input.empty;
}
}
void popFront()
{
assert(!empty, "Attempting to popFront an empty map.");
_input.popFront();
}
@property auto ref front()
{
assert(!empty, "Attempting to fetch the front of an empty map.");
return fun(_input.front);
}
static if (isRandomAccessRange!R)
{
static if (is(typeof(_input[ulong.max])))
private alias opIndex_t = ulong;
else
private alias opIndex_t = uint;
auto ref opIndex(opIndex_t index)
{
return fun(_input[index]);
}
}
static if (hasLength!R)
{
@property auto length()
{
return _input.length;
}
alias opDollar = length;
}
static if (hasSlicing!R)
{
static if (is(typeof(_input[ulong.max .. ulong.max])))
private alias opSlice_t = ulong;
else
private alias opSlice_t = uint;
static if (hasLength!R)
{
auto opSlice(opSlice_t low, opSlice_t high)
{
return typeof(this)(_input[low .. high]);
}
}
else static if (is(typeof(_input[opSlice_t.max .. $])))
{
struct DollarToken{}
enum opDollar = DollarToken.init;
auto opSlice(opSlice_t low, DollarToken)
{
return typeof(this)(_input[low .. $]);
}
auto opSlice(opSlice_t low, opSlice_t high)
{
import std.range : takeExactly;
return this[low .. $].takeExactly(high - low);
}
}
}
static if (isForwardRange!R)
{
@property auto save()
{
return typeof(this)(_input.save);
}
}
}
@safe unittest
{
import std.algorithm.comparison : equal;
import std.conv : to;
import std.functional : adjoin;
alias stringize = map!(to!string);
assert(equal(stringize([ 1, 2, 3, 4 ]), [ "1", "2", "3", "4" ]));
uint counter;
alias count = map!((a) { return counter++; });
assert(equal(count([ 10, 2, 30, 4 ]), [ 0, 1, 2, 3 ]));
counter = 0;
adjoin!((a) { return counter++; }, (a) { return counter++; })(1);
alias countAndSquare = map!((a) { return counter++; }, (a) { return counter++; });
//assert(equal(countAndSquare([ 10, 2 ]), [ tuple(0u, 100), tuple(1u, 4) ]));
}
@safe unittest
{
import std.algorithm.comparison : equal;
import std.ascii : toUpper;
import std.internal.test.dummyrange;
import std.range;
import std.typecons : tuple;
import std.random : uniform, Random = Xorshift;
int[] arr1 = [ 1, 2, 3, 4 ];
const int[] arr1Const = arr1;
int[] arr2 = [ 5, 6 ];
auto squares = map!("a * a")(arr1Const);
assert(squares[$ - 1] == 16);
assert(equal(squares, [ 1, 4, 9, 16 ][]));
assert(equal(map!("a * a")(chain(arr1, arr2)), [ 1, 4, 9, 16, 25, 36 ][]));
// Test the caching stuff.
assert(squares.back == 16);
auto squares2 = squares.save;
assert(squares2.back == 16);
assert(squares2.front == 1);
squares2.popFront();
assert(squares2.front == 4);
squares2.popBack();
assert(squares2.front == 4);
assert(squares2.back == 9);
assert(equal(map!("a * a")(chain(arr1, arr2)), [ 1, 4, 9, 16, 25, 36 ][]));
uint i;
foreach (e; map!("a", "a * a")(arr1))
{
assert(e[0] == ++i);
assert(e[1] == i * i);
}
// Test length.
assert(squares.length == 4);
assert(map!"a * a"(chain(arr1, arr2)).length == 6);
// Test indexing.
assert(squares[0] == 1);
assert(squares[1] == 4);
assert(squares[2] == 9);
assert(squares[3] == 16);
// Test slicing.
auto squareSlice = squares[1 .. squares.length - 1];
assert(equal(squareSlice, [4, 9][]));
assert(squareSlice.back == 9);
assert(squareSlice[1] == 9);
// Test on a forward range to make sure it compiles when all the fancy
// stuff is disabled.
auto fibsSquares = map!"a * a"(recurrence!("a[n-1] + a[n-2]")(1, 1));
assert(fibsSquares.front == 1);
fibsSquares.popFront();
fibsSquares.popFront();
assert(fibsSquares.front == 4);
fibsSquares.popFront();
assert(fibsSquares.front == 9);
auto repeatMap = map!"a"(repeat(1));
auto gen = Random(123_456_789);
auto index = uniform(0, 1024, gen);
static assert(isInfinite!(typeof(repeatMap)));
assert(repeatMap[index] == 1);
auto intRange = map!"a"([1,2,3]);
static assert(isRandomAccessRange!(typeof(intRange)));
assert(equal(intRange, [1, 2, 3]));
foreach (DummyType; AllDummyRanges)
{
DummyType d;
auto m = map!"a * a"(d);
static assert(propagatesRangeType!(typeof(m), DummyType));
assert(equal(m, [1,4,9,16,25,36,49,64,81,100]));
}
//Test string access
string s1 = "hello world!";
dstring s2 = "日本語";
dstring s3 = "hello world!"d;
auto ms1 = map!(std.ascii.toUpper)(s1);
auto ms2 = map!(std.ascii.toUpper)(s2);
auto ms3 = map!(std.ascii.toUpper)(s3);
static assert(!is(ms1[0])); //narrow strings can't be indexed
assert(ms2[0] == '');
assert(ms3[0] == 'H');
static assert(!is(ms1[0 .. 1])); //narrow strings can't be sliced
assert(equal(ms2[0 .. 2], "日本"w));
assert(equal(ms3[0 .. 2], "HE"));
// Issue 5753
static void voidFun(int) {}
static int nonvoidFun(int) { return 0; }
static assert(!__traits(compiles, map!voidFun([1])));
static assert(!__traits(compiles, map!(voidFun, voidFun)([1])));
static assert(!__traits(compiles, map!(nonvoidFun, voidFun)([1])));
static assert(!__traits(compiles, map!(voidFun, nonvoidFun)([1])));
static assert(!__traits(compiles, map!(a => voidFun(a))([1])));
// Phobos issue #15480
auto dd = map!(z => z * z, c => c * c * c)([ 1, 2, 3, 4 ]);
assert(dd[0] == tuple(1, 1));
assert(dd[1] == tuple(4, 8));
assert(dd[2] == tuple(9, 27));
assert(dd[3] == tuple(16, 64));
assert(dd.length == 4);
}
@safe unittest
{
import std.algorithm.comparison : equal;
import std.range;
auto LL = iota(1L, 4L);
auto m = map!"a*a"(LL);
assert(equal(m, [1L, 4L, 9L]));
}
@safe unittest
{
import std.range : iota;
// Issue #10130 - map of iota with const step.
const step = 2;
assert(map!(i => i)(iota(0, 10, step)).walkLength == 5);
// Need these to all by const to repro the float case, due to the
// CommonType template used in the float specialization of iota.
const floatBegin = 0.0;
const floatEnd = 1.0;
const floatStep = 0.02;
assert(map!(i => i)(iota(floatBegin, floatEnd, floatStep)).walkLength == 50);
}
@safe unittest
{
import std.algorithm.comparison : equal;
import std.range;
//slicing infinites
auto rr = iota(0, 5).cycle().map!"a * a"();
alias RR = typeof(rr);
static assert(hasSlicing!RR);
rr = rr[6 .. $]; //Advances 1 cycle and 1 unit
assert(equal(rr[0 .. 5], [1, 4, 9, 16, 0]));
}
@safe unittest
{
import std.range;
struct S {int* p;}
auto m = immutable(S).init.repeat().map!"a".save;
assert(m.front == immutable(S)(null));
}
// each
/**
Eagerly iterates over `r` and calls `fun` over _each element.
If no function to call is specified, `each` defaults to doing nothing but
consuming the entire range. `r.front` will be evaluated, but that can be avoided
by specifying a lambda with a `lazy` parameter.
`each` also supports `opApply`-based types, so it works with e.g. $(REF
parallel, std,parallelism).
Normally the entire range is iterated. If partial iteration (early stopping) is
desired, `fun` needs to return a value of type $(REF Flag,
std,typecons)`!"each"` (`Yes.each` to continue iteration, or `No.each` to stop
iteration).
Params:
fun = function to apply to _each element of the range
r = range or iterable over which `each` iterates
Returns: `Yes.each` if the entire range was iterated, `No.each` in case of early
stopping.
See_Also: $(REF tee, std,range)
*/
template each(alias fun = "a")
{
import std.meta : AliasSeq;
import std.traits : Parameters;
import std.typecons : Flag, Yes, No;
private:
alias BinaryArgs = AliasSeq!(fun, "i", "a");
enum isRangeUnaryIterable(R) =
is(typeof(unaryFun!fun(R.init.front)));
enum isRangeBinaryIterable(R) =
is(typeof(binaryFun!BinaryArgs(0, R.init.front)));
enum isRangeIterable(R) =
isInputRange!R &&
(isRangeUnaryIterable!R || isRangeBinaryIterable!R);
enum isForeachUnaryIterable(R) =
is(typeof((R r) {
foreach (ref a; r)
cast(void) unaryFun!fun(a);
}));
enum isForeachUnaryWithIndexIterable(R) =
is(typeof((R r) {
foreach (i, ref a; r)
cast(void) binaryFun!BinaryArgs(i, a);
}));
enum isForeachBinaryIterable(R) =
is(typeof((R r) {
foreach (ref a, ref b; r)
cast(void) binaryFun!fun(a, b);
}));
enum isForeachIterable(R) =
(!isForwardRange!R || isDynamicArray!R) &&
(isForeachUnaryIterable!R || isForeachBinaryIterable!R ||
isForeachUnaryWithIndexIterable!R);
public:
/**
Params:
r = range or iterable over which each iterates
*/
Flag!"each" each(Range)(Range r)
if (!isForeachIterable!Range && (
isRangeIterable!Range ||
__traits(compiles, typeof(r.front).length)))
{
static if (isRangeIterable!Range)
{
debug(each) pragma(msg, "Using while for ", Range.stringof);
static if (isRangeUnaryIterable!Range)
{
while (!r.empty)
{
static if (!is(typeof(unaryFun!fun(r.front)) == Flag!"each"))
{
cast(void) unaryFun!fun(r.front);
}
else
{
if (unaryFun!fun(r.front) == No.each) return No.each;
}
r.popFront();
}
}
else // if (isRangeBinaryIterable!Range)
{
size_t i = 0;
while (!r.empty)
{
static if (!is(typeof(binaryFun!BinaryArgs(i, r.front)) == Flag!"each"))
{
cast(void) binaryFun!BinaryArgs(i, r.front);
}
else
{
if (binaryFun!BinaryArgs(i, r.front) == No.each) return No.each;
}
r.popFront();
i++;
}
}
}
else
{
// range interface with >2 parameters.
for (auto range = r; !range.empty; range.popFront())
{
static if (!is(typeof(fun(r.front.expand)) == Flag!"each"))
{
cast(void) fun(range.front.expand);
}
else
{
if (fun(range.front.expand)) return No.each;
}
}
}
return Yes.each;
}
/// ditto
Flag!"each" each(Iterable)(auto ref Iterable r)
if (isForeachIterable!Iterable ||
__traits(compiles, Parameters!(Parameters!(r.opApply))))
{
static if (isForeachIterable!Iterable)
{
static if (isForeachUnaryIterable!Iterable)
{
debug(each) pragma(msg, "Using foreach UNARY for ", Iterable.stringof);
{
foreach (ref e; r)
{
static if (!is(typeof(unaryFun!fun(e)) == Flag!"each"))
{
cast(void) unaryFun!fun(e);
}
else
{
if (unaryFun!fun(e) == No.each) return No.each;
}
}
}
}
else static if (isForeachBinaryIterable!Iterable)
{
debug(each) pragma(msg, "Using foreach BINARY for ", Iterable.stringof);
foreach (ref a, ref b; r)
{
static if (!is(typeof(binaryFun!fun(a, b)) == Flag!"each"))
{
cast(void) binaryFun!fun(a, b);
}
else
{
if (binaryFun!fun(a, b) == No.each) return No.each;
}
}
}
else static if (isForeachUnaryWithIndexIterable!Iterable)
{
debug(each) pragma(msg, "Using foreach INDEX for ", Iterable.stringof);
foreach (i, ref e; r)
{
static if (!is(typeof(binaryFun!BinaryArgs(i, e)) == Flag!"each"))
{
cast(void) binaryFun!BinaryArgs(i, e);
}
else
{
if (binaryFun!BinaryArgs(i, e) == No.each) return No.each;
}
}
}
else
{
static assert(0, "Invalid foreach iteratable type " ~ Iterable.stringof ~ " met.");
}
return Yes.each;
}
else
{
// opApply with >2 parameters. count the delegate args.
// only works if it is not templated (otherwise we cannot count the args)
auto result = Yes.each;
auto dg(Parameters!(Parameters!(r.opApply)) params)
{
static if (!is(typeof(binaryFun!BinaryArgs(i, e)) == Flag!"each"))
{
fun(params);
return 0; // tells opApply to continue iteration
}
else
{
result = fun(params);
return result == Yes.each ? 0 : -1;
}
}
r.opApply(&dg);
return result;
}
}
}
///
@system unittest
{
import std.range : iota;
import std.typecons : Flag, Yes, No;
long[] arr;
iota(5).each!(n => arr ~= n);
assert(arr == [0, 1, 2, 3, 4]);
iota(5).each!((n) { arr ~= n; return No.each; });
assert(arr == [0, 1, 2, 3, 4, 0]);
// If the range supports it, the value can be mutated in place
arr.each!((ref n) => n++);
assert(arr == [1, 2, 3, 4, 5, 1]);
arr.each!"a++";
assert(arr == [2, 3, 4, 5, 6, 2]);
// by-ref lambdas are not allowed for non-ref ranges
static assert(!is(typeof(arr.map!(n => n).each!((ref n) => n++))));
// The default predicate consumes the range
auto m = arr.map!(n => n);
(&m).each();
assert(m.empty);
// Indexes are also available for in-place mutations
arr[] = 0;
arr.each!"a=i"();
assert(arr == [0, 1, 2, 3, 4, 5]);
// opApply iterators work as well
static class S
{
int x;
int opApply(scope int delegate(ref int _x) dg) { return dg(x); }
}
auto s = new S;
s.each!"a++";
assert(s.x == 1);
}
// binary foreach with two ref args
@system unittest
{
import std.range : lockstep;
auto a = [ 1, 2, 3 ];
auto b = [ 2, 3, 4 ];
a.lockstep(b).each!((ref x, ref y) { ++x; ++y; });
assert(a == [ 2, 3, 4 ]);
assert(b == [ 3, 4, 5 ]);
}
// #15358: application of `each` with >2 args (opApply)
@system unittest
{
import std.range : lockstep;
auto a = [0,1,2];
auto b = [3,4,5];
auto c = [6,7,8];
lockstep(a, b, c).each!((ref x, ref y, ref z) { ++x; ++y; ++z; });
assert(a == [1,2,3]);
assert(b == [4,5,6]);
assert(c == [7,8,9]);
}
// #15358: application of `each` with >2 args (range interface)
@safe unittest
{
import std.range : zip;
auto a = [0,1,2];
auto b = [3,4,5];
auto c = [6,7,8];
int[] res;
zip(a, b, c).each!((x, y, z) { res ~= x + y + z; });
assert(res == [9, 12, 15]);
}
// #16255: `each` on opApply doesn't support ref
@safe unittest
{
int[] dynamicArray = [1, 2, 3, 4, 5];
int[5] staticArray = [1, 2, 3, 4, 5];
dynamicArray.each!((ref x) => x++);
assert(dynamicArray == [2, 3, 4, 5, 6]);
staticArray.each!((ref x) => x++);
assert(staticArray == [2, 3, 4, 5, 6]);
staticArray[].each!((ref x) => x++);
assert(staticArray == [3, 4, 5, 6, 7]);
}
// #16255: `each` on opApply doesn't support ref
@system unittest
{
struct S
{
int x;
int opApply(int delegate(ref int _x) dg) { return dg(x); }
}
S s;
foreach (ref a; s) ++a;
assert(s.x == 1);
s.each!"++a";
assert(s.x == 2);
}
// #15357: `each` should behave similar to foreach
/// `each` works with iterable objects which provide an index variable, along with each element
@safe unittest
{
import std.range : iota, lockstep;
auto arr = [1, 2, 3, 4];
// 1 ref parameter
arr.each!((ref e) => e = 0);
assert(arr.sum == 0);
// 1 ref parameter and index
arr.each!((i, ref e) => e = cast(int) i);
assert(arr.sum == 4.iota.sum);
}
// #15357: `each` should behave similar to foreach
@system unittest
{
import std.range : iota, lockstep;
// 2 ref parameters and index
auto arrA = [1, 2, 3, 4];
auto arrB = [5, 6, 7, 8];
lockstep(arrA, arrB).each!((ref a, ref b) {
a = 0;
b = 1;
});
assert(arrA.sum == 0);
assert(arrB.sum == 4);
// 3 ref parameters
auto arrC = [3, 3, 3, 3];
lockstep(arrA, arrB, arrC).each!((ref a, ref b, ref c) {
a = 1;
b = 2;
c = 3;
});
assert(arrA.sum == 4);
assert(arrB.sum == 8);
assert(arrC.sum == 12);
}
// #15357: `each` should behave similar to foreach
@system unittest
{
import std.range : lockstep;
import std.typecons : Tuple;
auto a = "abc";
auto b = "def";
// print each character with an index
{
alias Element = Tuple!(size_t, "index", dchar, "value");
Element[] rForeach, rEach;
foreach (i, c ; a) rForeach ~= Element(i, c);
a.each!((i, c) => rEach ~= Element(i, c));
assert(rForeach == rEach);
assert(rForeach == [Element(0, 'a'), Element(1, 'b'), Element(2, 'c')]);
}
// print pairs of characters
{
alias Element = Tuple!(dchar, "a", dchar, "b");
Element[] rForeach, rEach;
foreach (c1, c2 ; a.lockstep(b)) rForeach ~= Element(c1, c2);
a.lockstep(b).each!((c1, c2) => rEach ~= Element(c1, c2));
assert(rForeach == rEach);
assert(rForeach == [Element('a', 'd'), Element('b', 'e'), Element('c', 'f')]);
}
}
// filter
/**
Implements the higher order filter function. The predicate is passed to
$(REF unaryFun, std,functional), and can either accept a string, or any callable
that can be executed via `pred(element)`.
Params:
predicate = Function to apply to each element of range
Returns:
`filter!(predicate)(range)` returns a new range containing only elements `x` in `range` for
which `predicate(x)` returns `true`.
See_Also:
$(HTTP en.wikipedia.org/wiki/Filter_(higher-order_function), Filter (higher-order function))
*/
template filter(alias predicate)
if (is(typeof(unaryFun!predicate)))
{
/**
Params:
range = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives)
of elements
Returns:
A range containing only elements `x` in `range` for
which `predicate(x)` returns `true`.
*/
auto filter(Range)(Range range) if (isInputRange!(Unqual!Range))
{
return FilterResult!(unaryFun!predicate, Range)(range);
}
}
///
@safe unittest
{
import std.algorithm.comparison : equal;
import std.math : approxEqual;
import std.range;
int[] arr = [ 1, 2, 3, 4, 5 ];
// Filter below 3
auto small = filter!(a => a < 3)(arr);
assert(equal(small, [ 1, 2 ]));
// Filter again, but with Uniform Function Call Syntax (UFCS)
auto sum = arr.filter!(a => a < 3);
assert(equal(sum, [ 1, 2 ]));
// In combination with chain() to span multiple ranges
int[] a = [ 3, -2, 400 ];
int[] b = [ 100, -101, 102 ];
auto r = chain(a, b).filter!(a => a > 0);
assert(equal(r, [ 3, 400, 100, 102 ]));
// Mixing convertible types is fair game, too
double[] c = [ 2.5, 3.0 ];
auto r1 = chain(c, a, b).filter!(a => cast(int) a != a);
assert(approxEqual(r1, [ 2.5 ]));
}
private struct FilterResult(alias pred, Range)
{
alias R = Unqual!Range;
R _input;
private bool _primed;
private void prime()
{
if (_primed) return;
while (!_input.empty && !pred(_input.front))
{
_input.popFront();
}
_primed = true;
}
this(R r)
{
_input = r;
}
private this(R r, bool primed)
{
_input = r;
_primed = primed;
}
auto opSlice() { return this; }
static if (isInfinite!Range)
{
enum bool empty = false;
}
else
{
@property bool empty() { prime; return _input.empty; }
}
void popFront()
{
do
{
_input.popFront();
} while (!_input.empty && !pred(_input.front));
_primed = true;
}
@property auto ref front()
{
prime;
assert(!empty, "Attempting to fetch the front of an empty filter.");
return _input.front;
}
static if (isForwardRange!R)
{
@property auto save()
{
return typeof(this)(_input.save, _primed);
}
}
}
@safe unittest
{
import std.algorithm.comparison : equal;
import std.internal.test.dummyrange;
import std.range;
auto shouldNotLoop4ever = repeat(1).filter!(x => x % 2 == 0);
static assert(isInfinite!(typeof(shouldNotLoop4ever)));
assert(!shouldNotLoop4ever.empty);
int[] a = [ 3, 4, 2 ];
auto r = filter!("a > 3")(a);
static assert(isForwardRange!(typeof(r)));
assert(equal(r, [ 4 ][]));
a = [ 1, 22, 3, 42, 5 ];
auto under10 = filter!("a < 10")(a);
assert(equal(under10, [1, 3, 5][]));
static assert(isForwardRange!(typeof(under10)));
under10.front = 4;
assert(equal(under10, [4, 3, 5][]));
under10.front = 40;
assert(equal(under10, [40, 3, 5][]));
under10.front = 1;
auto infinite = filter!"a > 2"(repeat(3));
static assert(isInfinite!(typeof(infinite)));
static assert(isForwardRange!(typeof(infinite)));
assert(infinite.front == 3);
foreach (DummyType; AllDummyRanges)
{
DummyType d;
auto f = filter!"a & 1"(d);
assert(equal(f, [1,3,5,7,9]));
static if (isForwardRange!DummyType)
{
static assert(isForwardRange!(typeof(f)));
}
}
// With delegates
int x = 10;
int overX(int a) { return a > x; }
typeof(filter!overX(a)) getFilter()
{
return filter!overX(a);
}
auto r1 = getFilter();
assert(equal(r1, [22, 42]));
// With chain
auto nums = [0,1,2,3,4];
assert(equal(filter!overX(chain(a, nums)), [22, 42]));
// With copying of inner struct Filter to Map
auto arr = [1,2,3,4,5];
auto m = map!"a + 1"(filter!"a < 4"(arr));
assert(equal(m, [2, 3, 4]));
}
@safe unittest
{
import std.algorithm.comparison : equal;
int[] a = [ 3, 4 ];
const aConst = a;
auto r = filter!("a > 3")(aConst);
assert(equal(r, [ 4 ][]));
a = [ 1, 22, 3, 42, 5 ];
auto under10 = filter!("a < 10")(a);
assert(equal(under10, [1, 3, 5][]));
assert(equal(under10.save, [1, 3, 5][]));
assert(equal(under10.save, under10));
}
@safe unittest
{
import std.algorithm.comparison : equal;
import std.functional : compose, pipe;
assert(equal(compose!(map!"2 * a", filter!"a & 1")([1,2,3,4,5]),
[2,6,10]));
assert(equal(pipe!(filter!"a & 1", map!"2 * a")([1,2,3,4,5]),
[2,6,10]));
}
@safe unittest
{
import std.algorithm.comparison : equal;
int x = 10;
int underX(int a) { return a < x; }
const(int)[] list = [ 1, 2, 10, 11, 3, 4 ];
assert(equal(filter!underX(list), [ 1, 2, 3, 4 ]));
}
/**
* Similar to `filter`, except it defines a
* $(REF_ALTTEXT bidirectional range, isBidirectionalRange, std,range,primitives).
* There is a speed disadvantage - the constructor spends time
* finding the last element in the range that satisfies the filtering
* condition (in addition to finding the first one). The advantage is
* that the filtered range can be spanned from both directions. Also,
* $(REF retro, std,range) can be applied against the filtered range.
*
* The predicate is passed to $(REF unaryFun, std,functional), and can either
* accept a string, or any callable that can be executed via `pred(element)`.
*
* Params:
* pred = Function to apply to each element of range
*/
template filterBidirectional(alias pred)
{
/**
Params:
r = Bidirectional range of elements
Returns:
A range containing only the elements in `r` for which `pred` returns `true`.
*/
auto filterBidirectional(Range)(Range r) if (isBidirectionalRange!(Unqual!Range))
{
return FilterBidiResult!(unaryFun!pred, Range)(r);
}
}
///
@safe unittest
{
import std.algorithm.comparison : equal;
import std.range;
int[] arr = [ 1, 2, 3, 4, 5 ];
auto small = filterBidirectional!("a < 3")(arr);
static assert(isBidirectionalRange!(typeof(small)));
assert(small.back == 2);
assert(equal(small, [ 1, 2 ]));
assert(equal(retro(small), [ 2, 1 ]));
// In combination with chain() to span multiple ranges
int[] a = [ 3, -2, 400 ];
int[] b = [ 100, -101, 102 ];
auto r = filterBidirectional!("a > 0")(chain(a, b));
assert(r.back == 102);
}
private struct FilterBidiResult(alias pred, Range)
{
alias R = Unqual!Range;
R _input;
this(R r)
{
_input = r;
while (!_input.empty && !pred(_input.front)) _input.popFront();
while (!_input.empty && !pred(_input.back)) _input.popBack();
}
@property bool empty() { return _input.empty; }
void popFront()
{
do
{
_input.popFront();
} while (!_input.empty && !pred(_input.front));
}
@property auto ref front()
{
assert(!empty, "Attempting to fetch the front of an empty filterBidirectional.");
return _input.front;
}
void popBack()
{
do
{
_input.popBack();
} while (!_input.empty && !pred(_input.back));
}
@property auto ref back()
{
assert(!empty, "Attempting to fetch the back of an empty filterBidirectional.");
return _input.back;
}
@property auto save()
{
return typeof(this)(_input.save);
}
}
/**
Groups consecutively equivalent elements into a single tuple of the element and
the number of its repetitions.
Similarly to `uniq`, `group` produces a range that iterates over unique
consecutive elements of the given range. Each element of this range is a tuple
of the element and the number of times it is repeated in the original range.
Equivalence of elements is assessed by using the predicate `pred`, which
defaults to `"a == b"`. The predicate is passed to $(REF binaryFun, std,functional),
and can either accept a string, or any callable that can be executed via
`pred(element, element)`.
Params:
pred = Binary predicate for determining equivalence of two elements.
R = The range type
r = The $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to
iterate over.
Returns: A range of elements of type `Tuple!(ElementType!R, uint)`,
representing each consecutively unique element and its respective number of
occurrences in that run. This will be an input range if `R` is an input
range, and a forward range in all other cases.
See_Also: $(LREF chunkBy), which chunks an input range into subranges
of equivalent adjacent elements.
*/
Group!(pred, Range) group(alias pred = "a == b", Range)(Range r)
{
return typeof(return)(r);
}
/// ditto
struct Group(alias pred, R)
if (isInputRange!R)
{
import std.typecons : Rebindable, tuple, Tuple;
private alias comp = binaryFun!pred;
private alias E = ElementType!R;
static if ((is(E == class) || is(E == interface)) &&
(is(E == const) || is(E == immutable)))
{
private alias MutableE = Rebindable!E;
}
else static if (is(E : Unqual!E))
{
private alias MutableE = Unqual!E;
}
else
{
private alias MutableE = E;
}
private R _input;
private Tuple!(MutableE, uint) _current;
///
this(R input)
{
_input = input;
if (!_input.empty) popFront();
}
///
void popFront()
{
if (_input.empty)
{
_current[1] = 0;
}
else
{
_current = tuple(_input.front, 1u);
_input.popFront();
while (!_input.empty && comp(_current[0], _input.front))
{
++_current[1];
_input.popFront();
}
}
}
static if (isInfinite!R)
{
///
enum bool empty = false; // Propagate infiniteness.
}
else
{
///
@property bool empty()
{
return _current[1] == 0;
}
}
/// Returns: the front of the range
@property auto ref front()
{
assert(!empty, "Attempting to fetch the front of an empty Group.");
return _current;
}
static if (isForwardRange!R)
{
///
@property typeof(this) save() {
typeof(this) ret = this;
ret._input = this._input.save;
ret._current = this._current;
return ret;
}
}
}
///
@safe unittest
{
import std.algorithm.comparison : equal;
import std.typecons : tuple, Tuple;
int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ];
assert(equal(group(arr), [ tuple(1, 1u), tuple(2, 4u), tuple(3, 1u),
tuple(4, 3u), tuple(5, 1u) ][]));
}
/**
* Using group, an associative array can be easily generated with the count of each
* unique element in the range.
*/
@safe unittest
{
import std.algorithm.sorting : sort;
import std.array : assocArray;
uint[string] result;
auto range = ["a", "b", "a", "c", "b", "c", "c", "d", "e"];
result = range.sort!((a, b) => a < b)
.group
.assocArray;
assert(result == ["a": 2U, "b": 2U, "c": 3U, "d": 1U, "e": 1U]);
}
@safe unittest
{
import std.algorithm.comparison : equal;
import std.internal.test.dummyrange;
import std.typecons : tuple, Tuple;
int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ];
assert(equal(group(arr), [ tuple(1, 1u), tuple(2, 4u), tuple(3, 1u),
tuple(4, 3u), tuple(5, 1u) ][]));
static assert(isForwardRange!(typeof(group(arr))));
foreach (DummyType; AllDummyRanges)
{
DummyType d;
auto g = group(d);
static assert(d.rt == RangeType.Input || isForwardRange!(typeof(g)));
assert(equal(g, [tuple(1, 1u), tuple(2, 1u), tuple(3, 1u), tuple(4, 1u),
tuple(5, 1u), tuple(6, 1u), tuple(7, 1u), tuple(8, 1u),
tuple(9, 1u), tuple(10, 1u)]));
}
}
@safe unittest
{
import std.algorithm.comparison : equal;
import std.typecons : tuple;
// Issue 13857
immutable(int)[] a1 = [1,1,2,2,2,3,4,4,5,6,6,7,8,9,9,9];
auto g1 = group(a1);
assert(equal(g1, [ tuple(1, 2u), tuple(2, 3u), tuple(3, 1u),
tuple(4, 2u), tuple(5, 1u), tuple(6, 2u),
tuple(7, 1u), tuple(8, 1u), tuple(9, 3u)
]));
// Issue 13162
immutable(ubyte)[] a2 = [1, 1, 1, 0, 0, 0];
auto g2 = a2.group;
assert(equal(g2, [ tuple(1, 3u), tuple(0, 3u) ]));
// Issue 10104
const a3 = [1, 1, 2, 2];
auto g3 = a3.group;
assert(equal(g3, [ tuple(1, 2u), tuple(2, 2u) ]));
interface I {}
class C : I {}
const C[] a4 = [new const C()];
auto g4 = a4.group!"a is b";
assert(g4.front[1] == 1);
immutable I[] a5 = [new immutable C()];
auto g5 = a5.group!"a is b";
assert(g5.front[1] == 1);
const(int[][]) a6 = [[1], [1]];
auto g6 = a6.group;
assert(equal(g6.front[0], [1]));
}
// Used by implementation of chunkBy for non-forward input ranges.
private struct ChunkByChunkImpl(alias pred, Range)
if (isInputRange!Range && !isForwardRange!Range)
{
alias fun = binaryFun!pred;
private Range r;
private ElementType!Range prev;
this(Range range, ElementType!Range _prev)
{
r = range;
prev = _prev;
}
@property bool empty()
{
return r.empty || !fun(prev, r.front);
}
@property ElementType!Range front() { return r.front; }
void popFront() { r.popFront(); }
}
private template ChunkByImplIsUnary(alias pred, Range)
{
static if (is(typeof(binaryFun!pred(ElementType!Range.init,
ElementType!Range.init)) : bool))
enum ChunkByImplIsUnary = false;
else static if (is(typeof(
unaryFun!pred(ElementType!Range.init) ==
unaryFun!pred(ElementType!Range.init))))
enum ChunkByImplIsUnary = true;
else
static assert(0, "chunkBy expects either a binary predicate or "~
"a unary predicate on range elements of type: "~
ElementType!Range.stringof);
}
// Implementation of chunkBy for non-forward input ranges.
private struct ChunkByImpl(alias pred, Range)
if (isInputRange!Range && !isForwardRange!Range)
{
enum bool isUnary = ChunkByImplIsUnary!(pred, Range);
static if (isUnary)
alias eq = binaryFun!((a, b) => unaryFun!pred(a) == unaryFun!pred(b));
else
alias eq = binaryFun!pred;
private Range r;
private ElementType!Range _prev;
this(Range _r)
{
r = _r;
if (!empty)
{
// Check reflexivity if predicate is claimed to be an equivalence
// relation.
assert(eq(r.front, r.front),
"predicate is not reflexive");
// _prev's type may be a nested struct, so must be initialized
// directly in the constructor (cannot call savePred()).
_prev = r.front;
}
else
{
// We won't use _prev, but must be initialized.
_prev = typeof(_prev).init;
}
}
@property bool empty() { return r.empty; }
@property auto front()
{
static if (isUnary)
{
import std.typecons : tuple;
return tuple(unaryFun!pred(_prev),
ChunkByChunkImpl!(eq, Range)(r, _prev));
}
else
{
return ChunkByChunkImpl!(eq, Range)(r, _prev);
}
}
void popFront()
{
while (!r.empty)
{
if (!eq(_prev, r.front))
{
_prev = r.front;
break;
}
r.popFront();
}
}
}
// Single-pass implementation of chunkBy for forward ranges.
private struct ChunkByImpl(alias pred, Range)
if (isForwardRange!Range)
{
import std.typecons : RefCounted;
enum bool isUnary = ChunkByImplIsUnary!(pred, Range);
static if (isUnary)
alias eq = binaryFun!((a, b) => unaryFun!pred(a) == unaryFun!pred(b));
else
alias eq = binaryFun!pred;
// Outer range
static struct Impl
{
size_t groupNum;
Range current;
Range next;
}
// Inner range
static struct Group
{
private size_t groupNum;
private Range start;
private Range current;
private RefCounted!Impl mothership;
this(RefCounted!Impl origin)
{
groupNum = origin.groupNum;
start = origin.current.save;
current = origin.current.save;
assert(!start.empty);
mothership = origin;
// Note: this requires reflexivity.
assert(eq(start.front, current.front),
"predicate is not reflexive");
}
@property bool empty() { return groupNum == size_t.max; }
@property auto ref front() { return current.front; }
void popFront()
{
current.popFront();
// Note: this requires transitivity.
if (current.empty || !eq(start.front, current.front))
{
if (groupNum == mothership.groupNum)
{
// If parent range hasn't moved on yet, help it along by
// saving location of start of next Group.
mothership.next = current.save;
}
groupNum = size_t.max;
}
}
@property auto save()
{
auto copy = this;
copy.current = current.save;
return copy;
}
}
static assert(isForwardRange!Group);
private RefCounted!Impl impl;
this(Range r)
{
impl = RefCounted!Impl(0, r, r.save);
}
@property bool empty() { return impl.current.empty; }
@property auto front()
{
static if (isUnary)
{
import std.typecons : tuple;
return tuple(unaryFun!pred(impl.current.front), Group(impl));
}
else
{
return Group(impl);
}
}
void popFront()
{
// Scan for next group. If we're lucky, one of our Groups would have
// already set .next to the start of the next group, in which case the
// loop is skipped.
while (!impl.next.empty && eq(impl.current.front, impl.next.front))
{
impl.next.popFront();
}
impl.current = impl.next.save;
// Indicate to any remaining Groups that we have moved on.
impl.groupNum++;
}
@property auto save()
{
// Note: the new copy of the range will be detached from any existing
// satellite Groups, and will not benefit from the .next acceleration.
return typeof(this)(impl.current.save);
}
static assert(isForwardRange!(typeof(this)));
}
@system unittest
{
import std.algorithm.comparison : equal;
size_t popCount = 0;
class RefFwdRange
{
int[] impl;
@safe nothrow:
this(int[] data) { impl = data; }
@property bool empty() { return impl.empty; }
@property auto ref front() { return impl.front; }
void popFront()
{
impl.popFront();
popCount++;
}
@property auto save() { return new RefFwdRange(impl); }
}
static assert(isForwardRange!RefFwdRange);
auto testdata = new RefFwdRange([1, 3, 5, 2, 4, 7, 6, 8, 9]);
auto groups = testdata.chunkBy!((a,b) => (a % 2) == (b % 2));
auto outerSave1 = groups.save;
// Sanity test
assert(groups.equal!equal([[1, 3, 5], [2, 4], [7], [6, 8], [9]]));
assert(groups.empty);
// Performance test for single-traversal use case: popFront should not have
// been called more times than there are elements if we traversed the
// segmented range exactly once.
assert(popCount == 9);
// Outer range .save test
groups = outerSave1.save;
assert(!groups.empty);
// Inner range .save test
auto grp1 = groups.front.save;
auto grp1b = grp1.save;
assert(grp1b.equal([1, 3, 5]));
assert(grp1.save.equal([1, 3, 5]));
// Inner range should remain consistent after outer range has moved on.
groups.popFront();
assert(grp1.save.equal([1, 3, 5]));
// Inner range should not be affected by subsequent inner ranges.
assert(groups.front.equal([2, 4]));
assert(grp1.save.equal([1, 3, 5]));
}
/**
* Chunks an input range into subranges of equivalent adjacent elements.
* In other languages this is often called `partitionBy`, `groupBy`
* or `sliceWhen`.
*
* Equivalence is defined by the predicate `pred`, which can be either
* binary, which is passed to $(REF binaryFun, std,functional), or unary, which is
* passed to $(REF unaryFun, std,functional). In the binary form, two range elements
* `a` and `b` are considered equivalent if `pred(a,b)` is true. In
* unary form, two elements are considered equivalent if `pred(a) == pred(b)`
* is true.
*
* This predicate must be an equivalence relation, that is, it must be
* reflexive (`pred(x,x)` is always true), symmetric
* (`pred(x,y) == pred(y,x)`), and transitive (`pred(x,y) && pred(y,z)`
* implies `pred(x,z)`). If this is not the case, the range returned by
* chunkBy may assert at runtime or behave erratically.
*
* Params:
* pred = Predicate for determining equivalence.
* r = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to be chunked.
*
* Returns: With a binary predicate, a range of ranges is returned in which
* all elements in a given subrange are equivalent under the given predicate.
* With a unary predicate, a range of tuples is returned, with the tuple
* consisting of the result of the unary predicate for each subrange, and the
* subrange itself.
*
* Notes:
*
* Equivalent elements separated by an intervening non-equivalent element will
* appear in separate subranges; this function only considers adjacent
* equivalence. Elements in the subranges will always appear in the same order
* they appear in the original range.
*
* See_also:
* $(LREF group), which collapses adjacent equivalent elements into a single
* element.
*/
auto chunkBy(alias pred, Range)(Range r)
if (isInputRange!Range)
{
return ChunkByImpl!(pred, Range)(r);
}
/// Showing usage with binary predicate:
/*FIXME: @safe*/ @system unittest
{
import std.algorithm.comparison : equal;
// Grouping by particular attribute of each element:
auto data = [
[1, 1],
[1, 2],
[2, 2],
[2, 3]
];
auto r1 = data.chunkBy!((a,b) => a[0] == b[0]);
assert(r1.equal!equal([
[[1, 1], [1, 2]],
[[2, 2], [2, 3]]
]));
auto r2 = data.chunkBy!((a,b) => a[1] == b[1]);
assert(r2.equal!equal([
[[1, 1]],
[[1, 2], [2, 2]],
[[2, 3]]
]));
}
version (none) // this example requires support for non-equivalence relations
@safe unittest
{
// Grouping by maximum adjacent difference:
import std.math : abs;
auto r3 = [1, 3, 2, 5, 4, 9, 10].chunkBy!((a, b) => abs(a-b) < 3);
assert(r3.equal!equal([
[1, 3, 2],
[5, 4],
[9, 10]
]));
}
/// Showing usage with unary predicate:
/* FIXME: pure @safe nothrow*/ @system unittest
{
import std.algorithm.comparison : equal;
import std.range.primitives;
import std.typecons : tuple;
// Grouping by particular attribute of each element:
auto range =
[
[1, 1],
[1, 1],
[1, 2],
[2, 2],
[2, 3],
[2, 3],
[3, 3]
];
auto byX = chunkBy!(a => a[0])(range);
auto expected1 =
[
tuple(1, [[1, 1], [1, 1], [1, 2]]),
tuple(2, [[2, 2], [2, 3], [2, 3]]),
tuple(3, [[3, 3]])
];
foreach (e; byX)
{
assert(!expected1.empty);
assert(e[0] == expected1.front[0]);
assert(e[1].equal(expected1.front[1]));
expected1.popFront();
}
auto byY = chunkBy!(a => a[1])(range);
auto expected2 =
[
tuple(1, [[1, 1], [1, 1]]),
tuple(2, [[1, 2], [2, 2]]),
tuple(3, [[2, 3], [2, 3], [3, 3]])
];
foreach (e; byY)
{
assert(!expected2.empty);
assert(e[0] == expected2.front[0]);
assert(e[1].equal(expected2.front[1]));
expected2.popFront();
}
}
/*FIXME: pure @safe nothrow*/ @system unittest
{
import std.algorithm.comparison : equal;
import std.typecons : tuple;
struct Item { int x, y; }
// Force R to have only an input range API with reference semantics, so
// that we're not unknowingly making use of array semantics outside of the
// range API.
class RefInputRange(R)
{
R data;
this(R _data) pure @safe nothrow { data = _data; }
@property bool empty() pure @safe nothrow { return data.empty; }
@property auto front() pure @safe nothrow { return data.front; }
void popFront() pure @safe nothrow { data.popFront(); }
}
auto refInputRange(R)(R range) { return new RefInputRange!R(range); }
{
auto arr = [ Item(1,2), Item(1,3), Item(2,3) ];
static assert(isForwardRange!(typeof(arr)));
auto byX = chunkBy!(a => a.x)(arr);
static assert(isForwardRange!(typeof(byX)));
auto byX_subrange1 = byX.front[1].save;
auto byX_subrange2 = byX.front[1].save;
static assert(isForwardRange!(typeof(byX_subrange1)));
static assert(isForwardRange!(typeof(byX_subrange2)));
byX.popFront();
assert(byX_subrange1.equal([ Item(1,2), Item(1,3) ]));
byX_subrange1.popFront();
assert(byX_subrange1.equal([ Item(1,3) ]));
assert(byX_subrange2.equal([ Item(1,2), Item(1,3) ]));
auto byY = chunkBy!(a => a.y)(arr);
static assert(isForwardRange!(typeof(byY)));
auto byY2 = byY.save;
static assert(is(typeof(byY) == typeof(byY2)));
byY.popFront();
assert(byY.front[0] == 3);
assert(byY.front[1].equal([ Item(1,3), Item(2,3) ]));
assert(byY2.front[0] == 2);
assert(byY2.front[1].equal([ Item(1,2) ]));
}
// Test non-forward input ranges.
{
auto range = refInputRange([ Item(1,1), Item(1,2), Item(2,2) ]);
auto byX = chunkBy!(a => a.x)(range);
assert(byX.front[0] == 1);
assert(byX.front[1].equal([ Item(1,1), Item(1,2) ]));
byX.popFront();
assert(byX.front[0] == 2);
assert(byX.front[1].equal([ Item(2,2) ]));
byX.popFront();
assert(byX.empty);
assert(range.empty);
range = refInputRange([ Item(1,1), Item(1,2), Item(2,2) ]);
auto byY = chunkBy!(a => a.y)(range);
assert(byY.front[0] == 1);
assert(byY.front[1].equal([ Item(1,1) ]));
byY.popFront();
assert(byY.front[0] == 2);
assert(byY.front[1].equal([ Item(1,2), Item(2,2) ]));
byY.popFront();
assert(byY.empty);
assert(range.empty);
}
}
// Issue 13595
version (none) // This requires support for non-equivalence relations
@system unittest
{
import std.algorithm.comparison : equal;
auto r = [1, 2, 3, 4, 5, 6, 7, 8, 9].chunkBy!((x, y) => ((x*y) % 3) == 0);
assert(r.equal!equal([
[1],
[2, 3, 4],
[5, 6, 7],
[8, 9]
]));
}
// Issue 13805
@system unittest
{
[""].map!((s) => s).chunkBy!((x, y) => true);
}
// joiner
/**
Lazily joins a range of ranges with a separator. The separator itself
is a range. If a separator is not provided, then the ranges are
joined directly without anything in between them (often called `flatten`
in other languages).
Params:
r = An $(REF_ALTTEXT input range, isInputRange, std,range,primitives) of input
ranges to be joined.
sep = A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) of
element(s) to serve as separators in the joined range.
Returns:
A range of elements in the joined range. This will be a forward range if
both outer and inner ranges of `RoR` are forward ranges; otherwise it will
be only an input range. The
$(REF_ALTTEXT range bidirectionality, isBidirectionalRange, std,range,primitives)
is propagated if no separator is specified.
See_also:
$(REF chain, std,range), which chains a sequence of ranges with compatible elements
into a single range.
*/
auto joiner(RoR, Separator)(RoR r, Separator sep)
if (isInputRange!RoR && isInputRange!(ElementType!RoR)
&& isForwardRange!Separator
&& is(ElementType!Separator : ElementType!(ElementType!RoR)))
{
static struct Result
{
private RoR _items;
private ElementType!RoR _current;
static if (isRandomAccessRange!Separator)
{
static struct CurrentSep
{
private Separator _sep;
private size_t sepIndex;
private size_t sepLength; // cache the length for performance
auto front() { return _sep[sepIndex]; }
void popFront() { sepIndex++; }
auto empty() { return sepIndex >= sepLength; }
auto save()
{
auto copy = this;
copy._sep = _sep;
return copy;
}
void reset()
{
sepIndex = 0;
}
void initialize(Separator sep)
{
_sep = sep;
sepIndex = sepLength = _sep.length;
}
}
}
else
{
static struct CurrentSep
{
private Separator _sep;
Separator payload;
alias payload this;
auto save()
{
auto copy = this;
copy._sep = _sep;
return copy;
}
void reset()
{
payload = _sep.save;
}
void initialize(Separator sep)
{
_sep = sep;
}
}
}
private CurrentSep _currentSep;
private void setItem()
{
if (!_items.empty)
{
// If we're exporting .save, we must not consume any of the
// subranges, since RoR.save does not guarantee that the states
// of the subranges are also saved.
static if (isForwardRange!RoR &&
isForwardRange!(ElementType!RoR))
_current = _items.front.save;
else
_current = _items.front;
}
}
private void useSeparator()
{
// Separator must always come after an item.
assert(_currentSep.empty && !_items.empty,
"joiner: internal error");
_items.popFront();
// If there are no more items, we're done, since separators are not
// terminators.
if (_items.empty) return;
if (_currentSep._sep.empty)
{
// Advance to the next range in the
// input
while (_items.front.empty)
{
_items.popFront();
if (_items.empty) return;
}
setItem;
}
else
{
_currentSep.reset;
assert(!_currentSep.empty);
}
}
this(RoR items, Separator sep)
{
_items = items;
_currentSep.initialize(sep);
//mixin(useItem); // _current should be initialized in place
if (_items.empty)
_current = _current.init; // set invalid state
else
{
// If we're exporting .save, we must not consume any of the
// subranges, since RoR.save does not guarantee that the states
// of the subranges are also saved.
static if (isForwardRange!RoR &&
isForwardRange!(ElementType!RoR))
_current = _items.front.save;
else
_current = _items.front;
if (_current.empty)
{
// No data in the current item - toggle to use the separator
useSeparator();
}
}
}
@property auto empty()
{
return _items.empty;
}
@property ElementType!(ElementType!RoR) front()
{
if (!_currentSep.empty) return _currentSep.front;
assert(!_current.empty, "Attempting to fetch the front of an empty joiner.");
return _current.front;
}
void popFront()
{
assert(!_items.empty, "Attempting to popFront an empty joiner.");
// Using separator?
if (!_currentSep.empty)
{
_currentSep.popFront();
if (_currentSep.empty && !_items.empty)
{
setItem;
if (_current.empty)
{
// No data in the current item - toggle to use the separator
useSeparator();
}
}
}
else
{
// we're using the range
_current.popFront();
if (_current.empty)
useSeparator();
}
}
static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR))
{
@property auto save()
{
Result copy = this;
copy._items = _items.save;
copy._current = _current.save;
copy._currentSep = _currentSep.save;
return copy;
}
}
}
return Result(r, sep);
}
///
@safe unittest
{
import std.algorithm.comparison : equal;
import std.conv : text;
assert(["abc", "def"].joiner.equal("abcdef"));
assert(["Mary", "has", "a", "little", "lamb"]
.joiner("...")
.equal("Mary...has...a...little...lamb"));
assert(["", "abc"].joiner("xyz").equal("xyzabc"));
assert([""].joiner("xyz").equal(""));
assert(["", ""].joiner("xyz").equal("xyz"));
}
@system unittest
{
import std.algorithm.comparison : equal;
import std.range.interfaces;
import std.range.primitives;
// joiner() should work for non-forward ranges too.
auto r = inputRangeObject(["abc", "def"]);
assert(equal(joiner(r, "xyz"), "abcxyzdef"));
}
@system unittest
{
import std.algorithm.comparison : equal;
import std.range;
// Related to issue 8061
auto r = joiner([
inputRangeObject("abc"),
inputRangeObject("def"),
], "-*-");
assert(equal(r, "abc-*-def"));
// Test case where separator is specified but is empty.
auto s = joiner([
inputRangeObject("abc"),
inputRangeObject("def"),
], "");
assert(equal(s, "abcdef"));
// Test empty separator with some empty elements
auto t = joiner([
inputRangeObject("abc"),
inputRangeObject(""),
inputRangeObject("def"),
inputRangeObject(""),
], "");
assert(equal(t, "abcdef"));
// Test empty elements with non-empty separator
auto u = joiner([
inputRangeObject(""),
inputRangeObject("abc"),
inputRangeObject(""),
inputRangeObject("def"),
inputRangeObject(""),
], "+-");
assert(equal(u, "+-abc+-+-def+-"));
// Issue 13441: only(x) as separator
string[][] lines = [null];
lines
.joiner(only("b"))
.array();
}
@safe unittest
{
import std.algorithm.comparison : equal;
// Transience correctness test
struct TransientRange
{
@safe:
int[][] src;
int[] buf;
this(int[][] _src)
{
src = _src;
buf.length = 100;
}
@property bool empty() { return src.empty; }
@property int[] front()
{
assert(src.front.length <= buf.length);
buf[0 .. src.front.length] = src.front[0..$];
return buf[0 .. src.front.length];
}
void popFront() { src.popFront(); }
}
// Test embedded empty elements
auto tr1 = TransientRange([[], [1,2,3], [], [4]]);
assert(equal(joiner(tr1, [0]), [0,1,2,3,0,0,4]));
// Test trailing empty elements
auto tr2 = TransientRange([[], [1,2,3], []]);
assert(equal(joiner(tr2, [0]), [0,1,2,3,0]));
// Test no empty elements
auto tr3 = TransientRange([[1,2], [3,4]]);
assert(equal(joiner(tr3, [0,1]), [1,2,0,1,3,4]));
// Test consecutive empty elements
auto tr4 = TransientRange([[1,2], [], [], [], [3,4]]);
assert(equal(joiner(tr4, [0,1]), [1,2,0,1,0,1,0,1,0,1,3,4]));
// Test consecutive trailing empty elements
auto tr5 = TransientRange([[1,2], [3,4], [], []]);
assert(equal(joiner(tr5, [0,1]), [1,2,0,1,3,4,0,1,0,1]));
}
@safe unittest
{
static assert(isInputRange!(typeof(joiner([""], ""))));
static assert(isForwardRange!(typeof(joiner([""], ""))));
}
/// Ditto
auto joiner(RoR)(RoR r)
if (isInputRange!RoR && isInputRange!(ElementType!RoR))
{
static struct Result
{
private:
RoR _items;
ElementType!RoR _current;
enum isBidirectional = isForwardRange!RoR && isForwardRange!(ElementType!RoR) &&
isBidirectionalRange!RoR && isBidirectionalRange!(ElementType!RoR);
static if (isBidirectional)
{
ElementType!RoR _currentBack;
bool reachedFinalElement;
}
this(RoR items, ElementType!RoR current)
{
_items = items;
_current = current;
static if (isBidirectional && hasNested!Result)
_currentBack = typeof(_currentBack).init;
}
public:
this(RoR r)
{
_items = r;
static if (isBidirectional && hasNested!Result)
_currentBack = typeof(_currentBack).init;
// field _current must be initialized in constructor, because it is nested struct
mixin(popFrontEmptyElements);
static if (isBidirectional)
mixin(popBackEmptyElements);
}
static if (isInfinite!RoR)
{
enum bool empty = false;
}
else
{
@property auto empty()
{
return _items.empty;
}
}
@property auto ref front()
{
assert(!empty, "Attempting to fetch the front of an empty joiner.");
return _current.front;
}
void popFront()
{
assert(!_current.empty, "Attempting to popFront an empty joiner.");
_current.popFront();
if (_current.empty)
{
assert(!_items.empty, "Attempting to popFront an empty joiner.");
_items.popFront();
mixin(popFrontEmptyElements);
}
}
private enum popFrontEmptyElements = q{
// Skip over empty subranges.
while (!_items.empty && _items.front.empty)
{
_items.popFront();
}
if (!_items.empty)
{
// We cannot export .save method unless we ensure subranges are not
// consumed when a .save'd copy of ourselves is iterated over. So
// we need to .save each subrange we traverse.
static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR))
_current = _items.front.save;
else
_current = _items.front;
}
else
{
_current = typeof(_current).init;
}
};
static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR))
{
@property auto save()
{
auto r = Result(_items.save, _current.save);
static if (isBidirectional)
r._currentBack = _currentBack.save;
return r;
}
}
static if (hasAssignableElements!(ElementType!RoR))
{
@property void front(ElementType!(ElementType!RoR) element)
{
assert(!empty, "Attempting to assign to front of an empty joiner.");
_current.front = element;
}
@property void front(ref ElementType!(ElementType!RoR) element)
{
assert(!empty, "Attempting to assign to front of an empty joiner.");
_current.front = element;
}
}
static if (isBidirectional)
{
bool checkFinalElement()
{
import std.range : dropOne;
if (reachedFinalElement)
return true;
static if (hasLength!(typeof(_items)))
{
if (_items.length == 1)
reachedFinalElement = true;
}
else
{
if (_items.save.dropOne.empty)
reachedFinalElement = true;
}
return false;
}
@property auto ref back()
{
assert(!empty, "Attempting to fetch the back of an empty joiner.");
if (reachedFinalElement)
return _current.back;
else
return _currentBack.back;
}
void popBack()
{
assert(!_current.empty, "Attempting to popBack an empty joiner.");
if (checkFinalElement)
_current.popBack();
else
_currentBack.popBack();
bool isEmpty = reachedFinalElement ? _current.empty : _currentBack.empty;
if (isEmpty)
{
assert(!_items.empty, "Attempting to popBack an empty joiner.");
_items.popBack();
mixin(popBackEmptyElements);
}
}
private enum popBackEmptyElements = q{
// Skip over empty subranges.
while (!_items.empty && _items.back.empty)
{
_items.popBack();
}
if (!_items.empty)
{
checkFinalElement;
// We cannot export .save method unless we ensure subranges are not
// consumed when a .save'd copy of ourselves is iterated over. So
// we need to .save each subrange we traverse.
static if (isForwardRange!RoR && isForwardRange!(ElementType!RoR))
{
if (reachedFinalElement)
_current = _items.back.save;
else
_currentBack = _items.back.save;
}
else
{
if (reachedFinalElement)
_current = _items.back;
else
_currentBack = _items.back;
}
}
else
{
_current = typeof(_current).init;
_currentBack = typeof(_currentBack).init;
}
};
static if (hasAssignableElements!(ElementType!RoR))
{
@property void back(ElementType!(ElementType!RoR) element)
{
assert(!empty, "Attempting to assign to back of an empty joiner.");
if (reachedFinalElement)
_current.back = element;
else
_currentBack.back = element;
}
@property void back(ref ElementType!(ElementType!RoR) element)
{
assert(!empty, "Attempting to assign to back of an empty joiner.");
if (reachedFinalElement)
_current.back = element;
else
_currentBack.back = element;
}
}
}
}
return Result(r);
}
///
@safe unittest
{
import std.algorithm.comparison : equal;
import std.range : repeat;
assert([""].joiner.equal(""));
assert(["", ""].joiner.equal(""));
assert(["", "abc"].joiner.equal("abc"));
assert(["abc", ""].joiner.equal("abc"));
assert(["abc", "def"].joiner.equal("abcdef"));
assert(["Mary", "has", "a", "little", "lamb"].joiner.equal("Maryhasalittlelamb"));
assert("abc".repeat(3).joiner.equal("abcabcabc"));
}
/// joiner allows in-place mutation!
@safe unittest
{
import std.algorithm.comparison : equal;
auto a = [ [1, 2, 3], [42, 43] ];
auto j = joiner(a);
j.front = 44;
assert(a == [ [44, 2, 3], [42, 43] ]);
assert(equal(j, [44, 2, 3, 42, 43]));
}
/// insert characters fully lazily into a string
@safe pure unittest
{
import std.algorithm.comparison : equal;
import std.range : chain, cycle, iota, only, retro, take, zip;
import std.format : format;
static immutable number = "12345678";
static immutable delimiter = ",";
auto formatted = number.retro
.zip(3.iota.cycle.take(number.length))
.map!(z => chain(z[0].only, z[1] == 2 ? delimiter : null))
.joiner
.retro;
static immutable expected = "12,345,678";
assert(formatted.equal(expected));
}
@safe unittest
{
import std.range.interfaces : inputRangeObject;
static assert(isInputRange!(typeof(joiner([""]))));
static assert(isForwardRange!(typeof(joiner([""]))));
}
@safe unittest
{
// Initial version of PR #6115 caused a compilation failure for
// https://github.com/BlackEdder/ggplotd/blob/d4428c08db5ffdc05dfd29690bf7da9073ea1dc5/source/ggplotd/stat.d#L562-L583
import std.range : zip;
int[] xCoords = [1, 2, 3];
int[] yCoords = [4, 5, 6];
auto coords = zip(xCoords, xCoords[1..$]).map!( (xr) {
return zip(yCoords, yCoords[1..$]).map!( (yr) {
return [
[[xr[0], xr[0], xr[1]],
[yr[0], yr[1], yr[1]]],
[[xr[0], xr[1], xr[1]],
[yr[0], yr[0], yr[1]]]
];
}).joiner;
}).joiner;
}
@system unittest
{
import std.algorithm.comparison : equal;
import std.range.interfaces : inputRangeObject;
import std.range : retro;
// bugzilla 8240
assert(equal(joiner([inputRangeObject("")]), ""));
assert(equal(joiner([inputRangeObject("")]).retro, ""));
// issue 8792
auto b = [[1], [2], [3]];
auto jb = joiner(b);
auto js = jb.save;
assert(equal(jb, js));
auto js2 = jb.save;
jb.popFront();
assert(!equal(jb, js));
assert(equal(js2, js));
js.popFront();
assert(equal(jb, js));
assert(!equal(js2, js));
}
// https://issues.dlang.org/show_bug.cgi?id=19213
@system unittest
{
auto results = [[1,2], [3,4]].map!(q => q.chunkBy!"a").joiner;
int i = 1;
foreach (ref e; results)
assert(e[0] == i++);
}
/// joiner can be bidirectional
@safe unittest
{
import std.algorithm.comparison : equal;
import std.range : retro;
auto a = [[1, 2, 3], [4, 5]];
auto j = a.joiner;
j.back = 44;
assert(a == [[1, 2, 3], [4, 44]]);
assert(equal(j.retro, [44, 4, 3, 2, 1]));
}
// bidirectional joiner: test for filtering empty elements
@safe unittest
{
import std.algorithm.comparison : equal;
import std.range : retro;
alias El = (e) => new int(e);
auto a = [null, [null, El(1), null, El(2), null, El(3), null], null, [null, El(4), null, El(5), null]];
auto j = a.joiner;
alias deref = a => a is null ? -1 : *a;
auto expected = [-1, 5, -1, 4, -1, -1, 3, -1, 2, -1, 1, -1];
// works with .save.
assert(j.save.retro.map!deref.equal(expected));
// and without .save
assert(j.retro.map!deref.equal(expected));
assert(j.retro.map!deref.equal(expected));
}
// bidirectional joiner is @nogc
@safe @nogc unittest
{
import std.algorithm.comparison : equal;
import std.range : iota, only, retro;
auto a = only(iota(1, 4), iota(4, 6));
auto j = a.joiner;
static immutable expected = [5 , 4, 3, 2, 1];
assert(equal(j.retro, expected));
}
// bidirectional joiner supports assignment to the back
@safe unittest
{
import std.algorithm.comparison : equal;
import std.range : popBackN;
auto a = [[1, 2, 3], [4, 5]];
auto j = a.joiner;
j.back = 55;
assert(a == [[1, 2, 3], [4, 55]]);
j.popBackN(2);
j.back = 33;
assert(a == [[1, 2, 33], [4, 55]]);
}
// bidirectional joiner works with auto-decoding
@safe unittest
{
import std.algorithm.comparison : equal;
import std.range : retro;
auto a = ["😀😐", "😠"];
auto j = a.joiner;
assert(j.retro.equal("😠😐😀"));
}
// test two-side iteration
@safe unittest
{
import std.algorithm.comparison : equal;
import std.range : popBackN;
auto arrs = [
[[1], [2], [3], [4], [5]],
[[1], [2, 3, 4], [5]],
[[1, 2, 3, 4, 5]],
];
foreach (arr; arrs)
{
auto a = arr.joiner;
assert(a.front == 1);
assert(a.back == 5);
a.popFront;
assert(a.front == 2);
assert(a.back == 5);
a.popBack;
assert(a.front == 2);
assert(a.back == 4);
a.popFront;
assert(a.front == 3);
assert(a.back == 4);
a.popBack;
assert(a.front == 3);
assert(a.back == 3);
a.popBack;
assert(a.empty);
}
}
@safe unittest
{
import std.algorithm.comparison : equal;
struct TransientRange
{
@safe:
int[] _buf;
int[][] _values;
this(int[][] values)
{
_values = values;
_buf = new int[128];
}
@property bool empty()
{
return _values.length == 0;
}
@property auto front()
{
foreach (i; 0 .. _values.front.length)
{
_buf[i] = _values[0][i];
}
return _buf[0 .. _values.front.length];
}
void popFront()
{
_values = _values[1 .. $];
}
}
auto rr = TransientRange([[1,2], [3,4,5], [], [6,7]]);
// Can't use array() or equal() directly because they fail with transient
// .front.
int[] result;
foreach (c; rr.joiner())
{
result ~= c;
}
assert(equal(result, [1,2,3,4,5,6,7]));
}
@safe unittest
{
import std.algorithm.comparison : equal;
import std.algorithm.internal : algoFormat;
struct TransientRange
{
@safe:
dchar[] _buf;
dstring[] _values;
this(dstring[] values)
{
_buf.length = 128;
_values = values;
}
@property bool empty()
{
return _values.length == 0;
}
@property auto front()
{
foreach (i; 0 .. _values.front.length)
{
_buf[i] = _values[0][i];
}
return _buf[0 .. _values.front.length];
}
void popFront()
{
_values = _values[1 .. $];
}
}
auto rr = TransientRange(["abc"d, "12"d, "def"d, "34"d]);
// Can't use array() or equal() directly because they fail with transient
// .front.
dchar[] result;
foreach (c; rr.joiner())
{
result ~= c;
}
import std.conv : to;
assert(equal(result, "abc12def34"d),
//Convert to string for assert's message
to!string("Unexpected result: '%s'"d.algoFormat(result)));
}
// Issue 8061
@system unittest
{
import std.conv : to;
import std.range.interfaces;
auto r = joiner([inputRangeObject("ab"), inputRangeObject("cd")]);
assert(isForwardRange!(typeof(r)));
auto str = to!string(r);
assert(str == "abcd");
}
@safe unittest
{
import std.range : repeat;
class AssignableRange
{
@safe:
int element;
@property int front()
{
return element;
}
alias back = front;
enum empty = false;
auto save()
{
return this;
}
void popFront() {}
alias popBack = popFront;
@property void front(int newValue)
{
element = newValue;
}
alias back = front;
}
static assert(isInputRange!AssignableRange);
static assert(is(ElementType!AssignableRange == int));
static assert(hasAssignableElements!AssignableRange);
static assert(!hasLvalueElements!AssignableRange);
auto range = new AssignableRange();
assert(range.element == 0);
{
auto joined = joiner(repeat(range));
joined.front = 5;
assert(range.element == 5);
assert(joined.front == 5);
joined.popFront;
int byRef = 7;
joined.front = byRef;
assert(range.element == byRef);
assert(joined.front == byRef);
}
{
auto joined = joiner(repeat(range));
joined.back = 5;
assert(range.element == 5);
assert(joined.back == 5);
joined.popBack;
int byRef = 7;
joined.back = byRef;
assert(range.element == byRef);
assert(joined.back == byRef);
}
}
/++
Implements the homonym function (also known as `accumulate`, $(D
compress), `inject`, or `foldl`) present in various programming
languages of functional flavor. There is also $(LREF fold) which does
the same thing but with the opposite parameter order.
The call `reduce!(fun)(seed, range)` first assigns `seed` to
an internal variable `result`, also called the accumulator.
Then, for each element `x` in `range`, `result = fun(result, x)`
gets evaluated. Finally, `result` is returned.
The one-argument version `reduce!(fun)(range)`
works similarly, but it uses the first element of the range as the
seed (the range must be non-empty).
Returns:
the accumulated `result`
Params:
fun = one or more functions
See_Also:
$(HTTP en.wikipedia.org/wiki/Fold_(higher-order_function), Fold (higher-order function))
$(LREF fold) is functionally equivalent to $(LREF _reduce) with the argument
order reversed, and without the need to use $(REF_ALTTEXT `tuple`,tuple,std,typecons)
for multiple seeds. This makes it easier to use in UFCS chains.
$(LREF sum) is similar to `reduce!((a, b) => a + b)` that offers
pairwise summing of floating point numbers.
+/
template reduce(fun...)
if (fun.length >= 1)
{
import std.meta : staticMap;
alias binfuns = staticMap!(binaryFun, fun);
static if (fun.length > 1)
import std.typecons : tuple, isTuple;
/++
No-seed version. The first element of `r` is used as the seed's value.
For each function `f` in `fun`, the corresponding
seed type `S` is `Unqual!(typeof(f(e, e)))`, where `e` is an
element of `r`: `ElementType!R` for ranges,
and `ForeachType!R` otherwise.
Once S has been determined, then `S s = e;` and `s = f(s, e);`
must both be legal.
Params:
r = an iterable value as defined by `isIterable`
Returns:
the final result of the accumulator applied to the iterable
Throws: `Exception` if `r` is empty
+/
auto reduce(R)(R r)
if (isIterable!R)
{
import std.exception : enforce;
alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R);
alias Args = staticMap!(ReduceSeedType!E, binfuns);
static if (isInputRange!R)
{
// no need to throw if range is statically known to be non-empty
static if (!__traits(compiles,
{
static assert(r.length > 0);
}))
enforce(!r.empty, "Cannot reduce an empty input range w/o an explicit seed value.");
Args result = r.front;
r.popFront();
return reduceImpl!false(r, result);
}
else
{
auto result = Args.init;
return reduceImpl!true(r, result);
}
}
/++
Seed version. The seed should be a single value if `fun` is a
single function. If `fun` is multiple functions, then `seed`
should be a $(REF Tuple, std,typecons), with one field per function in `f`.
For convenience, if the seed is const, or has qualified fields, then
`reduce` will operate on an unqualified copy. If this happens
then the returned type will not perfectly match `S`.
Use `fold` instead of `reduce` to use the seed version in a UFCS chain.
Params:
seed = the initial value of the accumulator
r = an iterable value as defined by `isIterable`
Returns:
the final result of the accumulator applied to the iterable
+/
auto reduce(S, R)(S seed, R r)
if (isIterable!R)
{
static if (fun.length == 1)
return reducePreImpl(r, seed);
else
{
import std.algorithm.internal : algoFormat;
static assert(isTuple!S, algoFormat("Seed %s should be a Tuple", S.stringof));
return reducePreImpl(r, seed.expand);
}
}
private auto reducePreImpl(R, Args...)(R r, ref Args args)
{
alias Result = staticMap!(Unqual, Args);
static if (is(Result == Args))
alias result = args;
else
Result result = args;
return reduceImpl!false(r, result);
}
private auto reduceImpl(bool mustInitialize, R, Args...)(R r, ref Args args)
if (isIterable!R)
{
import std.algorithm.internal : algoFormat;
static assert(Args.length == fun.length,
algoFormat("Seed %s does not have the correct amount of fields (should be %s)", Args.stringof, fun.length));
alias E = Select!(isInputRange!R, ElementType!R, ForeachType!R);
static if (mustInitialize) bool initialized = false;
foreach (/+auto ref+/ E e; r) // @@@4707@@@
{
foreach (i, f; binfuns)
{
static assert(!is(typeof(f(args[i], e))) || is(typeof(args[i] = f(args[i], e))),
algoFormat(
"Incompatible function/seed/element: %s/%s/%s",
fullyQualifiedName!f,
Args[i].stringof,
E.stringof
)
);
}
static if (mustInitialize) if (initialized == false)
{
import std.conv : emplaceRef;
foreach (i, f; binfuns)
emplaceRef!(Args[i])(args[i], e);
initialized = true;
continue;
}
foreach (i, f; binfuns)
args[i] = f(args[i], e);
}
static if (mustInitialize)
// no need to throw if range is statically known to be non-empty
static if (!__traits(compiles,
{
static assert(r.length > 0);
}))
{
if (!initialized)
throw new Exception("Cannot reduce an empty iterable w/o an explicit seed value.");
}
static if (Args.length == 1)
return args[0];
else
return tuple(args);
}
}
/**
Many aggregate range operations turn out to be solved with `reduce`
quickly and easily. The example below illustrates `reduce`'s
remarkable power and flexibility.
*/
@safe unittest
{
import std.algorithm.comparison : max, min;
import std.math : approxEqual;
import std.range;
int[] arr = [ 1, 2, 3, 4, 5 ];
// Sum all elements
auto sum = reduce!((a,b) => a + b)(0, arr);
assert(sum == 15);
// Sum again, using a string predicate with "a" and "b"
sum = reduce!"a + b"(0, arr);
assert(sum == 15);
// Compute the maximum of all elements
auto largest = reduce!(max)(arr);
assert(largest == 5);
// Max again, but with Uniform Function Call Syntax (UFCS)
largest = arr.reduce!(max);
assert(largest == 5);
// Compute the number of odd elements
auto odds = reduce!((a,b) => a + (b & 1))(0, arr);
assert(odds == 3);
// Compute the sum of squares
auto ssquares = reduce!((a,b) => a + b * b)(0, arr);
assert(ssquares == 55);
// Chain multiple ranges into seed
int[] a = [ 3, 4 ];
int[] b = [ 100 ];
auto r = reduce!("a + b")(chain(a, b));
assert(r == 107);
// Mixing convertible types is fair game, too
double[] c = [ 2.5, 3.0 ];
auto r1 = reduce!("a + b")(chain(a, b, c));
assert(approxEqual(r1, 112.5));
// To minimize nesting of parentheses, Uniform Function Call Syntax can be used
auto r2 = chain(a, b, c).reduce!("a + b");
assert(approxEqual(r2, 112.5));
}
/**
Sometimes it is very useful to compute multiple aggregates in one pass.
One advantage is that the computation is faster because the looping overhead
is shared. That's why `reduce` accepts multiple functions.
If two or more functions are passed, `reduce` returns a
$(REF Tuple, std,typecons) object with one member per passed-in function.
The number of seeds must be correspondingly increased.
*/
@safe unittest
{
import std.algorithm.comparison : max, min;
import std.math : approxEqual, sqrt;
import std.typecons : tuple, Tuple;
double[] a = [ 3.0, 4, 7, 11, 3, 2, 5 ];
// Compute minimum and maximum in one pass
auto r = reduce!(min, max)(a);
// The type of r is Tuple!(int, int)
assert(approxEqual(r[0], 2)); // minimum
assert(approxEqual(r[1], 11)); // maximum
// Compute sum and sum of squares in one pass
r = reduce!("a + b", "a + b * b")(tuple(0.0, 0.0), a);
assert(approxEqual(r[0], 35)); // sum
assert(approxEqual(r[1], 233)); // sum of squares
// Compute average and standard deviation from the above
auto avg = r[0] / a.length;
assert(avg == 5);
auto stdev = sqrt(r[1] / a.length - avg * avg);
assert(cast(int) stdev == 2);
}
@safe unittest
{
import std.algorithm.comparison : max, min;
import std.range : chain;
import std.typecons : tuple, Tuple;
double[] a = [ 3, 4 ];
auto r = reduce!("a + b")(0.0, a);
assert(r == 7);
r = reduce!("a + b")(a);
assert(r == 7);
r = reduce!(min)(a);
assert(r == 3);
double[] b = [ 100 ];
auto r1 = reduce!("a + b")(chain(a, b));
assert(r1 == 107);
// two funs
auto r2 = reduce!("a + b", "a - b")(tuple(0.0, 0.0), a);
assert(r2[0] == 7 && r2[1] == -7);
auto r3 = reduce!("a + b", "a - b")(a);
assert(r3[0] == 7 && r3[1] == -1);
a = [ 1, 2, 3, 4, 5 ];
// Stringize with commas
string rep = reduce!("a ~ `, ` ~ to!(string)(b)")("", a);
assert(rep[2 .. $] == "1, 2, 3, 4, 5", "["~rep[2 .. $]~"]");
}
@safe unittest
{
import std.algorithm.comparison : max, min;
import std.exception : assertThrown;
import std.range : iota;
import std.typecons : tuple, Tuple;
// Test the opApply case.
static struct OpApply
{
bool actEmpty;
int opApply(scope int delegate(ref int) @safe dg)
{
int res;
if (actEmpty) return res;
foreach (i; 0 .. 100)
{
res = dg(i);
if (res) break;
}
return res;
}
}
OpApply oa;
auto hundredSum = reduce!"a + b"(iota(100));
assert(reduce!"a + b"(5, oa) == hundredSum + 5);
assert(reduce!"a + b"(oa) == hundredSum);
assert(reduce!("a + b", max)(oa) == tuple(hundredSum, 99));
assert(reduce!("a + b", max)(tuple(5, 0), oa) == tuple(hundredSum + 5, 99));
// Test for throwing on empty range plus no seed.
assertThrown(reduce!"a + b"([1, 2][0 .. 0]));
oa.actEmpty = true;
assertThrown(reduce!"a + b"(oa));
}
@safe unittest
{
const float a = 0.0;
const float[] b = [ 1.2, 3, 3.3 ];
float[] c = [ 1.2, 3, 3.3 ];
auto r = reduce!"a + b"(a, b);
r = reduce!"a + b"(a, c);
assert(r == 7.5);
}
@safe unittest
{
// Issue #10408 - Two-function reduce of a const array.
import std.algorithm.comparison : max, min;
import std.typecons : tuple, Tuple;
const numbers = [10, 30, 20];
immutable m = reduce!(min)(numbers);
assert(m == 10);
immutable minmax = reduce!(min, max)(numbers);
assert(minmax == tuple(10, 30));
}
@safe unittest
{
//10709
import std.typecons : tuple, Tuple;
enum foo = "a + 0.5 * b";
auto r = [0, 1, 2, 3];
auto r1 = reduce!foo(r);
auto r2 = reduce!(foo, foo)(r);
assert(r1 == 3);
assert(r2 == tuple(3, 3));
}
@safe unittest
{
static struct OpApply
{
int opApply(int delegate(ref int) @safe dg)
{
int[] a = [1, 2, 3];
int res = 0;
foreach (ref e; a)
{
res = dg(e);
if (res) break;
}
return res;
}
}
//test CTFE and functions with context
int fun(int a, int b) @safe {return a + b + 1;}
auto foo()
{
import std.algorithm.comparison : max;
import std.typecons : tuple, Tuple;
auto a = reduce!(fun)([1, 2, 3]);
auto b = reduce!(fun, fun)([1, 2, 3]);
auto c = reduce!(fun)(0, [1, 2, 3]);
auto d = reduce!(fun, fun)(tuple(0, 0), [1, 2, 3]);
auto e = reduce!(fun)(0, OpApply());
auto f = reduce!(fun, fun)(tuple(0, 0), OpApply());
return max(a, b.expand, c, d.expand, e, f.expand);
}
auto a = foo();
assert(a == 9);
enum b = foo();
assert(b == 9);
}
@safe unittest
{
import std.algorithm.comparison : max, min;
import std.typecons : tuple, Tuple;
//http://forum.dlang.org/post/oghtttkopzjshsuflelk@forum.dlang.org
//Seed is tuple of const.
static auto minmaxElement(alias F = min, alias G = max, R)(in R range)
@safe pure nothrow
if (isInputRange!R)
{
return reduce!(F, G)(tuple(ElementType!R.max,
ElementType!R.min), range);
}
assert(minmaxElement([1, 2, 3]) == tuple(1, 3));
}
@safe unittest //12569
{
import std.algorithm.comparison : max, min;
import std.typecons : tuple;
dchar c = 'a';
reduce!(min, max)(tuple(c, c), "hello"); // OK
static assert(!is(typeof(reduce!(min, max)(tuple(c), "hello"))));
static assert(!is(typeof(reduce!(min, max)(tuple(c, c, c), "hello"))));
//"Seed dchar should be a Tuple"
static assert(!is(typeof(reduce!(min, max)(c, "hello"))));
//"Seed (dchar) does not have the correct amount of fields (should be 2)"
static assert(!is(typeof(reduce!(min, max)(tuple(c), "hello"))));
//"Seed (dchar, dchar, dchar) does not have the correct amount of fields (should be 2)"
static assert(!is(typeof(reduce!(min, max)(tuple(c, c, c), "hello"))));
//"Incompatable function/seed/element: all(alias pred = "a")/int/dchar"
static assert(!is(typeof(reduce!all(1, "hello"))));
static assert(!is(typeof(reduce!(all, all)(tuple(1, 1), "hello"))));
}
@safe unittest //13304
{
int[] data;
static assert(is(typeof(reduce!((a, b) => a + b)(data))));
assert(data.length == 0);
}
// https://issues.dlang.org/show_bug.cgi?id=13880
// reduce shouldn't throw if the length is statically known
pure nothrow @safe @nogc unittest
{
import std.algorithm.comparison : min;
int[5] arr;
arr[2] = -1;
assert(arr.reduce!min == -1);
int[0] arr0;
assert(reduce!min(42, arr0) == 42);
}
//Helper for Reduce
private template ReduceSeedType(E)
{
static template ReduceSeedType(alias fun)
{
import std.algorithm.internal : algoFormat;
alias ReduceSeedType = Unqual!(typeof(fun(lvalueOf!E, lvalueOf!E)));
//Check the Seed type is useable.
ReduceSeedType s = ReduceSeedType.init;
static assert(is(typeof({ReduceSeedType s = lvalueOf!E;})) &&
is(typeof(lvalueOf!ReduceSeedType = fun(lvalueOf!ReduceSeedType, lvalueOf!E))),
algoFormat(
"Unable to deduce an acceptable seed type for %s with element type %s.",
fullyQualifiedName!fun,
E.stringof
)
);
}
}
/++
Implements the homonym function (also known as `accumulate`, $(D
compress), `inject`, or `foldl`) present in various programming
languages of functional flavor. The call `fold!(fun)(range, seed)`
first assigns `seed` to an internal variable `result`,
also called the accumulator. Then, for each element `x` in $(D
range), `result = fun(result, x)` gets evaluated. Finally, $(D
result) is returned. The one-argument version `fold!(fun)(range)`
works similarly, but it uses the first element of the range as the
seed (the range must be non-empty).
Params:
fun = the predicate function(s) to apply to the elements
See_Also:
$(HTTP en.wikipedia.org/wiki/Fold_(higher-order_function), Fold (higher-order function))
$(LREF sum) is similar to `fold!((a, b) => a + b)` that offers
precise summing of floating point numbers.
This is functionally equivalent to $(LREF reduce) with the argument order
reversed, and without the need to use $(REF_ALTTEXT `tuple`,tuple,std,typecons)
for multiple seeds.
+/
template fold(fun...)
if (fun.length >= 1)
{
/**
Params:
r = the $(REF_ALTTEXT input range, isInputRange, std,range,primitives) to fold
seed = the initial value of the accumulator
Returns:
the accumulated `result`
*/
auto fold(R, S...)(R r, S seed)
{
static if (S.length < 2)
{
return reduce!fun(seed, r);
}