Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
// 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 splitWhen,
Lazily splits a range by comparing adjacent elements.)
$(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;
import std.functional : unaryFun, binaryFun;
import std.range.primitives;
import std.traits;
import std.typecons : Flag, Yes, No;
/++
`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);
}
// https://issues.dlang.org/show_bug.cgi?id=15891
@safe pure unittest
{
assert([1].map!(x=>[x].map!(y=>y)).cache.front.front == 1);
}
/++
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;
}
else
{
// needed, because the compiler cannot deduce, that 'caches' is initialized
// see https://issues.dlang.org/show_bug.cgi?id=15891
caches[0] = UE.init;
static if (bidir)
caches[1] = UE.init;
}
}
static if (isInfinite!R)
enum empty = false;
else
bool empty() @property
{
return source.empty;
}
mixin ImplementLength!source;
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
{
// see https://issues.dlang.org/show_bug.cgi?id=15891
caches[0] = UE.init;
static if (bidir)
caches[1] = UE.init;
}
}
static if (bidir) void popBack()
{
version (assert) if (empty) throw new RangeError();
source.popBack();
if (!source.empty)
caches[1] = source.back;
else
{
// see https://issues.dlang.org/show_bug.cgi?id=15891
caches[0] = UE.init;
caches[1] = UE.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 https://issues.dlang.org/show_bug.cgi?id=5710 is fixed
// accross all compilers (as of 2020-04, it wasn't fixed in LDC and GDC),
// 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
// https://issues.dlang.org/show_bug.cgi?id=15777.
static assert(!is(typeof(_fun(RE.init)) == void),
"Mapping function(s) must not return void: " ~ _funs.stringof);
}
return MapResult!(_fun, Range)(r);
}
}
///
@safe @nogc unittest
{
import std.algorithm.comparison : equal;
import std.range : chain, only;
auto squares =
chain(only(1, 2, 3, 4), only(5, 6)).map!(a => a * a);
assert(equal(squares, only(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" ]));
}
// Verify workaround for https://issues.dlang.org/show_bug.cgi?id=15777
@safe unittest
{
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(Range.init[ulong.max])))
private alias opIndex_t = ulong;
else
private alias opIndex_t = uint;
auto ref opIndex(opIndex_t index)
{
return fun(_input[index]);
}
}
mixin ImplementLength!_input;
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!(toUpper)(s1);
auto ms2 = map!(toUpper)(s2);
auto ms3 = map!(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"));
// https://issues.dlang.org/show_bug.cgi?id=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])));
// https://issues.dlang.org/show_bug.cgi?id=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;
// https://issues.dlang.org/show_bug.cgi?id=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));
}
// Issue 20928
@safe unittest
{
struct Always3
{
enum empty = false;
auto save() { return this; }
long front() { return 3; }
void popFront() {}
long opIndex(ulong i) { return 3; }
long opIndex(ulong i) immutable { return 3; }
}
import std.algorithm.iteration : map;
Always3.init.map!(e => e)[ulong.max];
}
// each
/**
Eagerly iterates over `r` and calls `fun` with _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;
}
}
}
///
@safe unittest
{
import std.range : iota;
import std.typecons : No;
int[] arr;
iota(5).each!(n => arr ~= n);
assert(arr == [0, 1, 2, 3, 4]);
// stop iterating early
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]);
auto m = arr.map!(n => n);
// by-ref lambdas are not allowed for non-ref ranges
static assert(!__traits(compiles, m.each!((ref n) => n++)));
// The default predicate consumes the range
(&m).each();
assert(m.empty);
}
/// `each` can pass an index variable for iterable objects which support this
@safe unittest
{
auto arr = new size_t[4];
arr.each!"a=i"();
assert(arr == [0, 1, 2, 3]);
arr.each!((i, ref e) => e = i * 2);
assert(arr == [0, 2, 4, 6]);
}
/// opApply iterators work as well
@system unittest
{
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 ]);
}
// https://issues.dlang.org/show_bug.cgi?id=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]);
}
// https://issues.dlang.org/show_bug.cgi?id=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]);
}
// https://issues.dlang.org/show_bug.cgi?id=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]);
}
// https://issues.dlang.org/show_bug.cgi?id=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);
}
// https://issues.dlang.org/show_bug.cgi?id=15357
// `each` should behave similar to foreach
@safe unittest
{
import std.range : iota;
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);
}
// https://issues.dlang.org/show_bug.cgi?id=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);
}
// https://issues.dlang.org/show_bug.cgi?id=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.operations : isClose;
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(isClose(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()
{
prime;
do
{
_input.popFront();
} while (!_input.empty && !pred(_input.front));
}
@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 ]));
}
// https://issues.dlang.org/show_bug.cgi?id=19823
@safe unittest
{
import std.algorithm.comparison : equal;
import std.range : dropOne;
auto a = [1, 2, 3, 4];
assert(a.filter!(a => a != 1).dropOne.equal([3, 4]));
assert(a.filter!(a => a != 2).dropOne.equal([3, 4]));
assert(a.filter!(a => a != 3).dropOne.equal([2, 4]));
assert(a.filter!(a => a != 4).dropOne.equal([2, 3]));
assert(a.filter!(a => a == 1).dropOne.empty);
assert(a.filter!(a => a == 2).dropOne.empty);
assert(a.filter!(a => a == 3).dropOne.empty);
assert(a.filter!(a => a == 4).dropOne.empty);
}
/**
* 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();
}
private this(R input, Tuple!(MutableE, uint) current)
{
_input = input;
_current = current;
}
///
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()
{
return Group(_input.save, _current);
}
}
}
///
@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;
// https://issues.dlang.org/show_bug.cgi?id=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)
]));
// https://issues.dlang.org/show_bug.cgi?id=13162
immutable(ubyte)[] a2 = [1, 1, 1, 0, 0, 0];
auto g2 = a2.group;
assert(equal(g2, [ tuple(1, 3u), tuple(0, 3u) ]));
// https://issues.dlang.org/show_bug.cgi?id=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 { override size_t toHash() const nothrow @safe { return 0; } }
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]));
}
@safe unittest
{
import std.algorithm.comparison : equal;
import std.typecons : tuple;
int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ];
auto r = arr.group;
assert(r.equal([ tuple(1,1u), tuple(2, 4u), tuple(3, 1u), tuple(4, 3u), tuple(5, 1u) ]));
r.popFront;
assert(r.equal([ tuple(2, 4u), tuple(3, 1u), tuple(4, 3u), tuple(5, 1u) ]));
auto s = r.save;
r.popFrontN(2);
assert(r.equal([ tuple(4, 3u), tuple(5, 1u) ]));
assert(s.equal([ tuple(2, 4u), tuple(3, 1u), tuple(4, 3u), tuple(5, 1u) ]));
s.popFront;
auto t = s.save;
r.popFront;
s.popFront;
assert(r.equal([ tuple(5, 1u) ]));
assert(s.equal([ tuple(4, 3u), tuple(5, 1u) ]));
assert(t.equal([ tuple(3, 1u), tuple(4, 3u), tuple(5, 1u) ]));
}
// https://issues.dlang.org/show_bug.cgi?id=18657
pure @safe unittest
{
import std.algorithm.comparison : equal;
import std.range : refRange;
string s = "foo";
auto r = refRange(&s).group;
assert(equal(r.save, "foo".group));
assert(equal(r, "foo".group));
}
// 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(ref Range range, ElementType!Range _prev)
{
r = &range;
prev = _prev;
}
@property bool empty()
{
return r.empty || !fun(prev, r.front);
}
@property ElementType!Range front()
{
assert(!empty, "Attempting to fetch the front of an empty chunkBy chunk.");
return r.front;
}
void popFront()
{
assert(!empty, "Attempting to popFront an empty chunkBy chunk.");
r.popFront();
}
}
private template ChunkByImplIsUnary(alias pred, Range)
{
alias e = lvalueOf!(ElementType!Range);
static if (is(typeof(binaryFun!pred(e, e)) : bool))
enum ChunkByImplIsUnary = false;
else static if (is(typeof(unaryFun!pred(e) == unaryFun!pred(e)) : bool))
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;
private bool openChunk = false;
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 && !openChunk; }
@property auto front()
{
assert(!empty, "Attempting to fetch the front of an empty chunkBy.");
openChunk = true;
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()
{
assert(!empty, "Attempting to popFront an empty chunkBy.");
openChunk = false;
while (!r.empty)
{
if (!eq(_prev, r.front))
{
_prev = r.front;
break;
}
r.popFront();
}
}
}
// Outer range for forward range version of chunkBy
private struct ChunkByOuter(Range, bool eqEquivalenceAssured)
{
size_t groupNum;
Range current;
Range next;
static if (!eqEquivalenceAssured)
{
bool nextUpdated;
}
}
// Inner range for forward range version of chunkBy
private struct ChunkByGroup(alias eq, Range, bool eqEquivalenceAssured)
{
import std.typecons : RefCounted;
alias OuterRange = ChunkByOuter!(Range, eqEquivalenceAssured);
private size_t groupNum;
static if (eqEquivalenceAssured)
{
private Range start;
}
private Range current;
// using union prevents RefCounted destructor from propagating @system to
// user code
union { private RefCounted!(OuterRange) mothership; }
private @trusted ref cargo() { return mothership.refCountedPayload; }
private this(ref RefCounted!(OuterRange) origin)
{
() @trusted { mothership = origin; }();
groupNum = cargo.groupNum;
current = cargo.current.save;
assert(!current.empty, "Passed range 'r' must not be empty");
static if (eqEquivalenceAssured)
{
start = cargo.current.save;
// Check for reflexivity.
assert(eq(start.front, current.front),
"predicate is not reflexive");
}
}
// Cannot be a copy constructor due to issue 22239
this(this) @trusted
{
import core.lifetime : emplace;
// since mothership has to be in a union, we have to manually trigger
// an increment to the reference count.
auto temp = mothership;
mothership = temp;
// prevents the reference count from falling back with brute force
emplace(&temp);
}
@property bool empty() { return groupNum == size_t.max; }
@property auto ref front() { return current.front; }
void popFront()
{
static if (!eqEquivalenceAssured)
{
auto prevElement = current.front;
}
current.popFront();
static if (eqEquivalenceAssured)
{
//this requires transitivity from the predicate.
immutable nowEmpty = current.empty || !eq(start.front, current.front);
}
else
{
immutable nowEmpty = current.empty || !eq(prevElement, current.front);
}
if (nowEmpty)
{
if (groupNum == cargo.groupNum)
{
// If parent range hasn't moved on yet, help it along by
// saving location of start of next Group.
cargo.next = current.save;
static if (!eqEquivalenceAssured)
{
cargo.nextUpdated = true;
}
}
groupNum = size_t.max;
}
}
@property auto save()
{
auto copy = this;
copy.current = current.save;
return copy;
}
@trusted ~this()
{
mothership.destroy;
}
}
private enum GroupingOpType{binaryEquivalent, binaryAny, unary}
// Single-pass implementation of chunkBy for forward ranges.
private struct ChunkByImpl(alias pred, alias eq, GroupingOpType opType, Range)
if (isForwardRange!Range)
{
import std.typecons : RefCounted;
enum bool eqEquivalenceAssured = opType != GroupingOpType.binaryAny;
alias OuterRange = ChunkByOuter!(Range, eqEquivalenceAssured);
alias InnerRange = ChunkByGroup!(eq, Range, eqEquivalenceAssured);
static assert(isForwardRange!InnerRange);
// using union prevents RefCounted destructor from propagating @system to
// user code
union { private RefCounted!OuterRange _impl; }
private @trusted ref impl() { return _impl; }
private @trusted ref implPL() { return _impl.refCountedPayload; }
this(Range r)
{
import core.lifetime : move;
auto savedR = r.save;
static if (eqEquivalenceAssured) () @trusted
{
_impl = RefCounted!OuterRange(0, r, savedR.move);
}();
else () @trusted
{
_impl = RefCounted!OuterRange(0, r, savedR.move, false);
}();
}
// Cannot be a copy constructor due to issue 22239
this(this) @trusted
{
import core.lifetime : emplace;
// since _impl has to be in a union, we have to manually trigger
// an increment to the reference count.
auto temp = _impl;
_impl = temp;
// prevents the reference count from falling back with brute force
emplace(&temp);
}
@property bool empty() { return implPL.current.empty; }
static if (opType == GroupingOpType.unary) @property auto front()
{
import std.typecons : tuple;
return tuple(unaryFun!pred(implPL.current.front), InnerRange(impl));
}
else @property auto front()
{
return InnerRange(impl);
}
static if (eqEquivalenceAssured) 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 (!implPL.next.empty && eq(implPL.current.front, implPL.next.front))
{
implPL.next.popFront();
}
implPL.current = implPL.next.save;
// Indicate to any remaining Groups that we have moved on.
implPL.groupNum++;
}
else void popFront()
{
if (implPL.nextUpdated)
{
implPL.current = implPL.next.save;
}
else while (true)
{
auto prevElement = implPL.current.front;
implPL.current.popFront();
if (implPL.current.empty) break;
if (!eq(prevElement, implPL.current.front)) break;
}
implPL.nextUpdated = false;
// Indicate to any remaining Groups that we have moved on.
implPL.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)(implPL.current.save);
}
static assert(isForwardRange!(typeof(this)), typeof(this).stringof
~ " must be a forward range");
@trusted ~this()
{
_impl.destroy;
}
}
//Test for https://issues.dlang.org/show_bug.cgi?id=14909
@safe unittest
{
import std.algorithm.comparison : equal;
import std.typecons : tuple;
import std.stdio;
auto n = 3;
auto s = [1,2,3].chunkBy!(a => a+n);
auto t = s.save.map!(x=>x[0]);
auto u = s.map!(x=>x[1]);
assert(t.equal([4,5,6]));
assert(u.equal!equal([[1],[2],[3]]));
}
//Testing inferring @system correctly
@safe unittest
{
struct DeadlySave
{
int front;
@safe void popFront(){front++;}
@safe bool empty(){return front >= 5;}
@system auto save(){return this;}
}
auto test1()
{
DeadlySave src;
return src.walkLength;
}
auto test2()
{
DeadlySave src;
return src.chunkBy!((a,b) => a % 2 == b % 2).walkLength;
}
static assert(isSafe!test1);
static assert(!isSafe!test2);
}
//Test for https://issues.dlang.org/show_bug.cgi?id=18751
@safe unittest
{
import std.algorithm.comparison : equal;
string[] data = [ "abc", "abc", "def" ];
int[] indices = [ 0, 1, 2 ];
auto chunks = indices.chunkBy!((i, j) => data[i] == data[j]);
assert(chunks.equal!equal([ [ 0, 1 ], [ 2 ] ]));
}
//Additional test for fix for issues 14909 and 18751
@safe unittest
{
import std.algorithm.comparison : equal;
auto v = [2,4,8,3,6,9,1,5,7];
auto i = 2;
assert(v.chunkBy!((a,b) => a % i == b % i).equal!equal([[2,4,8],[3],[6],[9,1,5,7]]));
}
@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. Use $(LREF splitWhen)
* if you want to chunk by a predicate that is not an equivalence relation.
*
* 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. Copying the range currently has reference semantics, but this may
* change in the future.
*
* 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)
{
static if (ChunkByImplIsUnary!(pred, Range))
{
enum opType = GroupingOpType.unary;
alias eq = binaryFun!((a, b) => unaryFun!pred(a) == unaryFun!pred(b));
}
else
{
enum opType = GroupingOpType.binaryEquivalent;
alias eq = binaryFun!pred;
}
static if (isForwardRange!Range)
return ChunkByImpl!(pred, eq, opType, Range)(r);
else
return ChunkByImpl!(pred, Range)(r);
}
/// Showing usage with binary predicate:
@safe 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]]
]));
}
/// Showing usage with unary predicate:
/* FIXME: pure nothrow*/ @safe 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 { assert(!empty); return data.front; }
void popFront() pure @safe nothrow { assert(!empty); data.popFront(); }
}
auto refInputRange(R)(R range) { return new RefInputRange!R(range); }
// An input range API with value semantics.
struct ValInputRange(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 { assert(!empty); return data.front; }
void popFront() pure @safe nothrow { assert(!empty); data.popFront(); }
}
auto valInputRange(R)(R range) { return ValInputRange!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 with reference semantics.
{
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);
}
// Test non-forward input ranges with value semantics.
{
auto range = valInputRange([ 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); // Opposite of refInputRange test
range = valInputRange([ 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); // Opposite of refInputRange test
}
/* https://issues.dlang.org/show_bug.cgi?id=19532
* General behavior of non-forward input ranges.
*
* - If the same chunk is retrieved multiple times via front, the separate chunk
* instances refer to a shared range segment that advances as a single range.
* - Emptying a chunk via popFront does not implicitly popFront the chunk off
* main range. The chunk is still available via front, it is just empty.
*/
{
import std.algorithm.comparison : equal;
import core.exception : AssertError;
import std.exception : assertThrown;
auto a = [[0, 0], [0, 1],
[1, 2], [1, 3], [1, 4],
[2, 5], [2, 6],
[3, 7],
[4, 8]];
// Value input range
{
auto r = valInputRange(a).chunkBy!((a, b) => a[0] == b[0]);
size_t numChunks = 0;
while (!r.empty)
{
++numChunks;
auto chunk = r.front;
while (!chunk.empty)
{
assert(r.front.front[1] == chunk.front[1]);
chunk.popFront;
}
assert(!r.empty);
assert(r.front.empty);
r.popFront;
}
assert(numChunks == 5);
// Now front and popFront should assert.
bool thrown = false;
try r.front;
catch (AssertError) thrown = true;
assert(thrown);
thrown = false;
try r.popFront;
catch (AssertError) thrown = true;
assert(thrown);
}
// Reference input range
{
auto r = refInputRange(a).chunkBy!((a, b) => a[0] == b[0]);
size_t numChunks = 0;
while (!r.empty)
{
++numChunks;
auto chunk = r.front;
while (!chunk.empty)
{
assert(r.front.front[1] == chunk.front[1]);
chunk.popFront;
}
assert(!r.empty);
assert(r.front.empty);
r.popFront;
}
assert(numChunks == 5);
// Now front and popFront should assert.
bool thrown = false;
try r.front;
catch (AssertError) thrown = true;
assert(thrown);
thrown = false;
try r.popFront;
catch (AssertError) thrown = true;
assert(thrown);
}
// Ensure that starting with an empty range doesn't create an empty chunk.
{
int[] emptyRange = [];
auto r1 = valInputRange(emptyRange).chunkBy!((a, b) => a == b);
auto r2 = refInputRange(emptyRange).chunkBy!((a, b) => a == b);
assert(r1.empty);
assert(r2.empty);
bool thrown = false;
try r1.front;
catch (AssertError) thrown = true;
assert(thrown);
thrown = false;
try r1.popFront;
catch (AssertError) thrown = true;
assert(thrown);
thrown = false;
try r2.front;
catch (AssertError) thrown = true;
assert(thrown);
thrown = false;
try r2.popFront;
catch (AssertError) thrown = true;
assert(thrown);
}
}
// https://issues.dlang.org/show_bug.cgi?id=19532 - Using roundRobin/chunkBy
{
import std.algorithm.comparison : equal;
import std.range : roundRobin;
auto a0 = [0, 1, 3, 6];
auto a1 = [0, 2, 4, 6, 7];
auto a2 = [1, 2, 4, 6, 8, 8, 9];
auto expected =
[[0, 0], [1, 1], [2, 2], [3], [4, 4], [6, 6, 6], [7], [8, 8], [9]];
auto r1 = roundRobin(valInputRange(a0), valInputRange(a1), valInputRange(a2))
.chunkBy!((a, b) => a == b);
assert(r1.equal!equal(expected));
auto r2 = roundRobin(refInputRange(a0), refInputRange(a1), refInputRange(a2))
.chunkBy!((a, b) => a == b);
assert(r2.equal!equal(expected));
auto r3 = roundRobin(a0, a1, a2).chunkBy!((a, b) => a == b);
assert(r3.equal!equal(expected));
}
// https://issues.dlang.org/show_bug.cgi?id=19532 - Using merge/chunkBy
{
import std.algorithm.comparison : equal;
import std.algorithm.sorting : merge;
auto a0 = [2, 3, 5];
auto a1 = [2, 4, 5];
auto a2 = [1, 2, 4, 5];
auto expected = [[1], [2, 2, 2], [3], [4, 4], [5, 5, 5]];
auto r1 = merge(valInputRange(a0), valInputRange(a1), valInputRange(a2))
.chunkBy!((a, b) => a == b);
assert(r1.equal!equal(expected));
auto r2 = merge(refInputRange(a0), refInputRange(a1), refInputRange(a2))
.chunkBy!((a, b) => a == b);
assert(r2.equal!equal(expected));
auto r3 = merge(a0, a1, a2).chunkBy!((a, b) => a == b);
assert(r3.equal!equal(expected));
}
// https://issues.dlang.org/show_bug.cgi?id=19532 - Using chunkBy/map-fold
{
import std.algorithm.comparison : equal;
import std.algorithm.iteration : fold, map;
auto a = [0, 0, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7, 8, 8, 9];
auto expected = [0, 3, 4, 6, 8, 5, 18, 7, 16, 9];
auto r1 = a
.chunkBy!((a, b) => a == b)
.map!(c => c.fold!((a, b) => a + b));
assert(r1.equal(expected));
auto r2 = valInputRange(a)
.chunkBy!((a, b) => a == b)
.map!(c => c.fold!((a, b) => a + b));
assert(r2.equal(expected));
auto r3 = refInputRange(a)
.chunkBy!((a, b) => a == b)
.map!(c => c.fold!((a, b) => a + b));
assert(r3.equal(expected));
}
// https://issues.dlang.org/show_bug.cgi?id=16169
// https://issues.dlang.org/show_bug.cgi?id=17966
// https://issues.dlang.org/show_bug.cgi?id=19532
// Using multiwayMerge/chunkBy
{
import std.algorithm.comparison : equal;
import std.algorithm.setops : multiwayMerge;
{
auto a0 = [2, 3, 5];
auto a1 = [2, 4, 5];
auto a2 = [1, 2, 4, 5];
auto expected = [[1], [2, 2, 2], [3], [4, 4], [5, 5, 5]];
auto r = multiwayMerge([a0, a1, a2]).chunkBy!((a, b) => a == b);
assert(r.equal!equal(expected));
}
{
auto a0 = [2, 3, 5];
auto a1 = [2, 4, 5];
auto a2 = [1, 2, 4, 5];
auto expected = [[1], [2, 2, 2], [3], [4, 4], [5, 5, 5]];
auto r =
multiwayMerge([valInputRange(a0), valInputRange(a1), valInputRange(a2)])
.chunkBy!((a, b) => a == b);
assert(r.equal!equal(expected));
}
{
auto a0 = [2, 3, 5];
auto a1 = [2, 4, 5];
auto a2 = [1, 2, 4, 5];
auto expected = [[1], [2, 2, 2], [3], [4, 4], [5, 5, 5]];
auto r =
multiwayMerge([refInputRange(a0), refInputRange(a1), refInputRange(a2)])
.chunkBy!((a, b) => a == b);
assert(r.equal!equal(expected));
}
}
// https://issues.dlang.org/show_bug.cgi?id=20496
{
auto r = [1,1,1,2,2,2,3,3,3];
r.chunkBy!((ref e1, ref e2) => e1 == e2);
}
}
// https://issues.dlang.org/show_bug.cgi?id=13805
@safe unittest
{
[""].map!((s) => s).chunkBy!((x, y) => true);
}
/**
Splits a forward range into subranges in places determined by a binary
predicate.
When iterating, one element of `r` is compared with `pred` to the next
element. If `pred` return true, a new subrange is started for the next element.
Otherwise, they are part of the same subrange.
If the elements are compared with an inequality (!=) operator, consider
$(LREF chunkBy) instead, as it's likely faster to execute.
Params:
pred = Predicate for determining where to split. The earlier element in the
source range is always given as the first argument.
r = A $(REF_ALTTEXT forward range, isForwardRange, std,range,primitives) to be split.
Returns: a range of subranges of `r`, split such that within a given subrange,
calling `pred` with any pair of adjacent elements as arguments returns `false`.
Copying the range currently has reference semantics, but this may change in the future.
See_also:
$(LREF splitter), which uses elements as splitters instead of element-to-element
relations.
*/
auto splitWhen(alias pred, Range)(Range r)
if (isForwardRange!Range)
{ import std.functional : not;
return ChunkByImpl!(not!pred, not!pred, GroupingOpType.binaryAny, Range)(r);
}
///
nothrow pure @safe unittest
{
import std.algorithm.comparison : equal;
import std.range : dropExactly;
auto source = [4, 3, 2, 11, 0, -3, -3, 5, 3, 0];
auto result1 = source.splitWhen!((a,b) => a <= b);
assert(result1.save.equal!equal([
[4, 3, 2],
[11, 0, -3],
[-3],
[5, 3, 0]
]));
//splitWhen, like chunkBy, is currently a reference range (this may change
//in future). Remember to call `save` when appropriate.
auto result2 = result1.dropExactly(2);
assert(result1.save.equal!equal([
[-3],
[5, 3, 0]
]));
}
//ensure we don't iterate the underlying range twice
nothrow @safe unittest
{
import std.algorithm.comparison : equal;
import std.math.algebraic : abs;
struct SomeRange
{
int[] elements;
static int popfrontsSoFar;
auto front(){return elements[0];}
nothrow @safe void popFront()
{ popfrontsSoFar++;
elements = elements[1 .. $];
}
auto empty(){return elements.length == 0;}
auto save(){return this;}
}
auto result = SomeRange([10, 9, 8, 5, 0, 1, 0, 8, 11, 10, 8, 12])
.splitWhen!((a, b) => abs(a - b) >= 3);
assert(result.equal!equal([
[10, 9, 8],
[5],
[0, 1, 0],
[8],
[11, 10, 8],
[12]
]));
assert(SomeRange.popfrontsSoFar == 12);
}
// Issue 13595
@safe unittest
{
import std.algorithm.comparison : equal;
auto r = [1, 2, 3, 4, 5, 6, 7, 8, 9].splitWhen!((x, y) => ((x*y) % 3) > 0);
assert(r.equal!equal([
[1],
[2, 3, 4],
[5, 6, 7],
[8, 9]
]));
}
nothrow pure @safe unittest
{
// Grouping by maximum adjacent difference:
import std.math.algebraic : abs;
import std.algorithm.comparison : equal;
auto r3 = [1, 3, 2, 5, 4, 9, 10].splitWhen!((a, b) => abs(a-b) >= 3);
assert(r3.equal!equal([
[1, 3, 2],
[5, 4],
[9, 10]
]));
}
// empty range splitWhen
@nogc nothrow pure @system unittest
{
int[1] sliceable;
auto result = sliceable[0 .. 0].splitWhen!((a,b) => a+b > 10);
assert(result.empty);
}
// 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 bidirectional range if
both outer and inner ranges of `RoR` are at least bidirectional ranges. Else if
both outer and inner ranges of `RoR` are forward ranges, the returned range will
be likewise. 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.
Note:
When both outer and inner ranges of `RoR` are bidirectional and the joiner is
iterated from the back to the front, the separator will still be consumed from
front to back, even if it is a bidirectional range too.
*/
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;
bool inputStartsWithEmpty = false;
static if (isBidirectional)
{
private ElementType!RoR _currentBack;
bool inputEndsWithEmpty = false;
}
enum isBidirectional = isBidirectionalRange!RoR &&
isBidirectionalRange!(ElementType!RoR);
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;
static if (isBidirectional)
{
private CurrentSep _currentBackSep;
}
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,
"Attempting to reset a non-empty separator");
assert(!_items.empty,
"Attempting to use a separator in an empty joiner");
_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, "separator must not be empty");
}
}
this(RoR items, Separator sep)
{
_items = items;
_currentSep.initialize(sep);
static if (isBidirectional)
_currentBackSep.initialize(sep);
//mixin(useItem); // _current should be initialized in place
if (_items.empty)
{
_current = _current.init; // set invalid state
static if (isBidirectional)
_currentBack = _currentBack.init;
}
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;
static if (isBidirectional)
{
_currentBack = _items.back.save;
if (_currentBack.empty)
{
// No data in the currentBack item - toggle to use
// the separator
inputEndsWithEmpty = true;
}
}
if (_current.empty)
{
// No data in the current item - toggle to use the separator
inputStartsWithEmpty = true;
// If RoR contains a single empty element,
// the returned Result will always be empty
import std.range : dropOne;
static if (hasLength!RoR)
{
if (_items.length == 1)
_items.popFront;
}
else static if (isForwardRange!RoR)
{
if (_items.save.dropOne.empty)
_items.popFront;
}
else
{
auto _itemsCopy = _items;
if (_itemsCopy.dropOne.empty)
_items.popFront;
}
}
}
}
@property auto empty()
{
return _items.empty;
}
//no data in the first item of the initial range - use the separator
private enum useSepIfFrontIsEmpty = q{
if (inputStartsWithEmpty)
{
useSeparator();
inputStartsWithEmpty = false;
}
};
@property ElementType!(ElementType!RoR) front()
{
mixin(useSepIfFrontIsEmpty);
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?
mixin(useSepIfFrontIsEmpty);
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;
static if (isBidirectional)
{
copy._currentBack = _currentBack;
copy._currentBackSep = _currentBackSep;
}
return copy;
}
}
static if (isBidirectional)
{
//no data in the last item of the initial range - use the separator
private enum useSepIfBackIsEmpty = q{
if (inputEndsWithEmpty)
{
useBackSeparator;
inputEndsWithEmpty = false;
}
};
private void setBackItem()
{
if (!_items.empty)
{
_currentBack = _items.back.save;
}
}
private void useBackSeparator()
{
// Separator must always come after an item.
assert(_currentBackSep.empty,
"Attempting to reset a non-empty separator");
assert(!_items.empty,
"Attempting to use a separator in an empty joiner");
_items.popBack();
// If there are no more items, we're done, since separators are not
// terminators.
if (_items.empty) return;
if (_currentBackSep._sep.empty)
{
// Advance to the next range in the
// input
while (_items.back.empty)
{
_items.popBack();
if (_items.empty) return;
}
setBackItem;
}
else
{
_currentBackSep.reset;
assert(!_currentBackSep.empty, "separator must not be empty");
}
}
@property ElementType!(ElementType!RoR) back()
{
mixin(useSepIfBackIsEmpty);
if (!_currentBackSep.empty) return _currentBackSep.front;
assert(!_currentBack.empty, "Attempting to fetch the back of an empty joiner.");
return _currentBack.back;
}
void popBack()
{
assert(!_items.empty, "Attempting to popBack an empty joiner.");
mixin(useSepIfBackIsEmpty);
if (!_currentBackSep.empty)
{
_currentBackSep.popFront();
if (_currentBackSep.empty && !_items.empty)
{
setBackItem;
if (_currentBack.empty)
{
// No data in the current item - toggle to use the separator
useBackSeparator();
}
}
}
else
{
// we're using the range
_currentBack.popBack();
if (_currentBack.empty)
useBackSeparator();
}
}
}
}
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"));
}
@safe pure nothrow unittest
{
//joiner with separator can return a bidirectional range
assert(isBidirectionalRange!(typeof(["abc", "def"].joiner("..."))));
}
@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 https://issues.dlang.org/show_bug.cgi?id=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+-"));
// https://issues.dlang.org/show_bug.cgi?id=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([""], ""))));
}
@safe pure unittest
{
{
import std.algorithm.comparison : equal;
auto r = joiner(["abc", "def", "ghi"], "?!");
char[] res;
while (!r.empty)
{
res ~= r.back;
r.popBack;
}
assert(res.equal("ihg?!fed?!cba"));
}
{
wchar[] sep = ['Ș', 'Ț'];
auto r = joiner(["","abc",""],sep);
wchar[] resFront;
wchar[] resBack;
auto rCopy = r.save;
while (!r.empty)
{
resFront ~= r.front;
r.popFront;
}
while (!rCopy.empty)
{
resBack ~= rCopy.back;
rCopy.popBack;
}
import std.algorithm.comparison : equal;
assert(resFront.equal("ȘȚabcȘȚ"));
assert(resBack.equal("ȘȚcbaȘȚ"));
}
{
import std.algorithm.comparison : equal;
auto r = [""];
r.popBack;
assert(r.joiner("AB").equal(""));
}
{
auto r = ["", "", "", "abc", ""].joiner("../");
auto rCopy = r.save;
char[] resFront;
char[] resBack;
while (!r.empty)
{
resFront ~= r.front;
r.popFront;
}
while (!rCopy.empty)
{
resBack ~= rCopy.back;
rCopy.popBack;
}
import std.algorithm.comparison : equal;
assert(resFront.equal("../../../abc../"));
assert(resBack.equal("../cba../../../"));
}
{
auto r = ["", "abc", ""].joiner("./");
auto rCopy = r.save;
r.popBack;
rCopy.popFront;
auto rRev = r.save;
auto rCopyRev = rCopy.save;
char[] r1, r2, r3, r4;
while (!r.empty)
{
r1 ~= r.back;
r.popBack;
}
while (!rCopy.empty)
{
r2 ~= rCopy.front;
rCopy.popFront;
}
while (!rRev.empty)
{
r3 ~= rRev.front;
rRev.popFront;
}
while (!rCopyRev.empty)
{
r4 ~= rCopyRev.back;
rCopyRev.popBack;
}
import std.algorithm.comparison : equal;
assert(r1.equal("/cba./"));
assert(r2.equal("/abc./"));
assert(r3.equal("./abc"));
assert(r4.equal("./cba"));
}
}
@system unittest
{
import std.range;
import std.algorithm.comparison : equal;
assert(inputRangeObject([""]).joiner("lz").equal(""));
}
@safe pure unittest
{
struct inputRangeStrings
{
private string[] strings;
string front()
{
return strings[0];
}
void popFront()
{
strings = strings[1..$];
}
bool empty() const
{
return strings.length == 0;
}
}
auto arr = inputRangeStrings([""]);
import std.algorithm.comparison : equal;
assert(arr.joiner("./").equal(""));
}
@safe pure unittest
{
auto r = joiner(["", "", "abc", "", ""],