tanya/source/tanya/range/primitive.d

2036 lines
45 KiB
D

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This module defines primitives for working with ranges.
*
* Copyright: Eugene Wissner 2017-2018.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/range/primitive.d,
* tanya/range/primitive.d)
*/
module tanya.range.primitive;
import tanya.algorithm.comparison;
import tanya.algorithm.mutation;
import tanya.meta.trait;
import tanya.meta.transform;
import tanya.range.array;
/**
* Returns the element type of the range $(D_PARAM R).
*
* Element type is the return type of such primitives like
* $(D_INLINECODE R.front) and (D_INLINECODE R.back) or the array base type.
* If $(D_PARAM R) is not a range, its element type is $(D_KEYWORD void).
*
* If $(D_PARAM R) is a string, $(D_PSYMBOL ElementType) doesn't distinguish
* between narrow and wide strings, it just returns the base type of the
* underlying array ($(D_KEYWORD char), $(D_KEYWORD wchar) or
* $(D_KEYWORD dchar)).
*
* Params:
* R = Range type.
*
* Returns: Element type of the range $(D_PARAM R).
*/
template ElementType(R)
{
static if (is(R U : U[]))
{
alias ElementType = U;
}
else static if (isInputRange!R)
{
alias ElementType = ReturnType!((R r) => r.front());
}
else
{
alias ElementType = void;
}
}
/**
* Detects whether $(D_PARAM R) has a length property.
*
* $(D_PARAM R) does not have to be a range to support the length.
*
* Length mustn't be a $(D_KEYWORD @property) or a function, it can be a member
* variable or $(D_KEYWORD enum). But its type (or the type returned by the
* appropriate function) should be $(D_KEYWORD size_t), otherwise
* $(D_PSYMBOL hasLength) is $(D_KEYWORD false).
*
* All dynamic arrays except $(D_KEYWORD void)-arrays have length.
*
* Params:
* R = A type.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM R) has a length property,
* $(D_KEYWORD false) otherwise.
*
* See_Also: $(D_PSYMBOL isInfinite).
*/
template hasLength(R)
{
enum bool hasLength = is(ReturnType!((R r) => r.length) == size_t);
}
///
@nogc nothrow pure @safe unittest
{
static assert(hasLength!(char[]));
static assert(hasLength!(int[]));
static assert(hasLength!(const(int)[]));
struct A
{
enum size_t length = 1;
}
static assert(hasLength!(A));
struct B
{
@property size_t length() const @nogc nothrow pure @safe
{
return 0;
}
}
static assert(hasLength!(B));
struct C
{
@property const(size_t) length() const @nogc nothrow pure @safe
{
return 0;
}
}
static assert(!hasLength!C);
}
/**
* Determines whether $(D_PARAM R) is a forward range with slicing support
* ($(D_INLINECODE R[i .. j])).
*
* For finite ranges, the result of `opSlice()` must be of the same type as the
* original range. If the range defines opDollar, it must support subtraction.
*
* For infinite ranges, the result of `opSlice()` must be of the same type as
* the original range only if it defines `opDollar()`. Otherwise it can be any
* forward range.
*
* For both finite and infinite ranges, the result of `opSlice()` must have
* length.
*
* Params:
* R = The type to be tested.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM R) supports slicing,
* $(D_KEYWORD false) otherwise.
*/
template hasSlicing(R)
{
private enum bool hasDollar = is(typeof((R r) => r[0 .. $]));
private enum bool subDollar = !hasDollar
|| isInfinite!R
|| is(ReturnType!((R r) => r[0 .. $ - 1]) == R);
static if (isForwardRange!R
&& is(ReturnType!((R r) => r[0 .. 0]) T)
&& (!hasDollar || is(ReturnType!((R r) => r[0 .. $]) == R))
&& subDollar
&& isForwardRange!(ReturnType!((ref R r) => r[0 .. 0])))
{
enum bool hasSlicing = (is(T == R) || isInfinite!R)
&& hasLength!T;
}
else
{
enum bool hasSlicing = false;
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(hasSlicing!(int[]));
static assert(hasSlicing!(const(int)[]));
static assert(hasSlicing!(dstring));
static assert(hasSlicing!(string));
static assert(!hasSlicing!(const int[]));
static assert(!hasSlicing!(void[]));
struct A
{
int front() @nogc nothrow pure @safe
{
return 0;
}
void popFront() @nogc nothrow pure @safe
{
}
bool empty() const @nogc nothrow pure @safe
{
return false;
}
typeof(this) save() @nogc nothrow pure @safe
{
return this;
}
@property size_t length() const @nogc nothrow pure @safe
{
return 0;
}
typeof(this) opSlice(const size_t i, const size_t j)
pure nothrow @safe @nogc
{
return this;
}
}
static assert(hasSlicing!A);
struct B
{
struct Dollar
{
}
int front() @nogc nothrow pure @safe
{
return 0;
}
void popFront() @nogc nothrow pure @safe
{
}
bool empty() const @nogc nothrow pure @safe
{
return false;
}
typeof(this) save() @nogc nothrow pure @safe
{
return this;
}
@property size_t length() const @nogc nothrow pure @safe
{
return 0;
}
@property Dollar opDollar() const @nogc nothrow pure @safe
{
return Dollar();
}
typeof(this) opSlice(const size_t i, const Dollar j)
pure nothrow @safe @nogc
{
return this;
}
}
static assert(!hasSlicing!B);
struct C
{
int front() @nogc nothrow pure @safe
{
return 0;
}
void popFront() @nogc nothrow pure @safe
{
}
enum bool empty = false;
typeof(this) save() @nogc nothrow pure @safe
{
return this;
}
typeof(this) opSlice(const size_t i, const size_t j)
pure nothrow @safe @nogc
{
return this;
}
}
static assert(!hasSlicing!C);
struct D
{
struct Range
{
int front() @nogc nothrow pure @safe
{
return 0;
}
void popFront() @nogc nothrow pure @safe
{
}
bool empty() const @nogc nothrow pure @safe
{
return true;
}
typeof(this) save() @nogc nothrow pure @safe
{
return this;
}
@property size_t length() const @nogc nothrow pure @safe
{
return 0;
}
}
int front() @nogc nothrow pure @safe
{
return 0;
}
void popFront() @nogc nothrow pure @safe
{
}
enum bool empty = false;
typeof(this) save() @nogc nothrow pure @safe
{
return this;
}
Range opSlice(const size_t i, const size_t j)
pure nothrow @safe @nogc
{
return Range();
}
}
static assert(hasSlicing!D);
}
version (unittest)
{
mixin template InputRangeStub()
{
@property int front() @nogc nothrow pure @safe
{
return 0;
}
@property bool empty() const @nogc nothrow pure @safe
{
return false;
}
void popFront() @nogc nothrow pure @safe
{
}
}
mixin template BidirectionalRangeStub()
{
@property int back() @nogc nothrow pure @safe
{
return 0;
}
void popBack() @nogc nothrow pure @safe
{
}
}
}
private template isDynamicArrayRange(R)
{
static if (is(R E : E[]))
{
enum bool isDynamicArrayRange = !is(E == void);
}
else
{
enum bool isDynamicArrayRange = false;
}
}
/**
* Determines whether $(D_PARAM R) is an input range.
*
* An input range should define following primitives:
*
* $(UL
* $(LI front)
* $(LI empty)
* $(LI popFront)
* )
*
* Params:
* R = The type to be tested.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM R) is an input range,
* $(D_KEYWORD false) otherwise.
*/
template isInputRange(R)
{
static if (is(ReturnType!((R r) => r.front()) U)
&& is(ReturnType!((R r) => r.empty) == bool)
&& is(typeof(R.popFront())))
{
enum bool isInputRange = !is(U == void);
}
else
{
enum bool isInputRange = isDynamicArrayRange!R;
}
}
///
@nogc nothrow pure @safe unittest
{
static struct Range
{
void popFront() @nogc nothrow pure @safe
{
}
int front() @nogc nothrow pure @safe
{
return 0;
}
bool empty() const @nogc nothrow pure @safe
{
return true;
}
}
static assert(isInputRange!Range);
static assert(isInputRange!(int[]));
static assert(!isInputRange!(void[]));
}
@nogc nothrow pure @safe unittest
{
static struct Range1(T)
{
void popFront()
{
}
int front()
{
return 0;
}
T empty() const
{
return true;
}
}
static assert(!isInputRange!(Range1!int));
static assert(!isInputRange!(Range1!(const bool)));
static struct Range2
{
int popFront() @nogc nothrow pure @safe
{
return 100;
}
int front() @nogc nothrow pure @safe
{
return 100;
}
bool empty() const @nogc nothrow pure @safe
{
return true;
}
}
static assert(isInputRange!Range2);
static struct Range3
{
void popFront() @nogc nothrow pure @safe
{
}
void front() @nogc nothrow pure @safe
{
}
bool empty() const @nogc nothrow pure @safe
{
return true;
}
}
static assert(!isInputRange!Range3);
static struct Range4
{
void popFront() @nogc nothrow pure @safe
{
}
int front() @nogc nothrow pure @safe
{
return 0;
}
enum bool empty = false;
}
static assert(isInputRange!Range4);
}
/**
* Determines whether $(D_PARAM R) is a forward range.
*
* A forward range is an input range that also defines:
*
* $(UL
* $(LI save)
* )
*
* Params:
* R = The type to be tested.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM R) is a forward range,
* $(D_KEYWORD false) otherwise.
*
* See_Also: $(D_PSYMBOL isInputRange).
*/
template isForwardRange(R)
{
static if (is(ReturnType!((R r) => r.save()) U))
{
enum bool isForwardRange = isInputRange!R && is(U == R);
}
else
{
enum bool isForwardRange = false;
}
}
///
@nogc nothrow pure @safe unittest
{
static struct Range
{
void popFront() @nogc nothrow pure @safe
{
}
int front() @nogc nothrow pure @safe
{
return 0;
}
bool empty() const @nogc nothrow pure @safe
{
return true;
}
typeof(this) save() @nogc nothrow pure @safe
{
return this;
}
}
static assert(isForwardRange!Range);
static assert(isForwardRange!(int[]));
static assert(!isForwardRange!(void[]));
}
@nogc nothrow pure @safe unittest
{
static struct Range1
{
}
static struct Range2
{
mixin InputRangeStub;
Range1 save() @nogc nothrow pure @safe
{
return Range1();
}
}
static assert(!isForwardRange!Range2);
static struct Range3
{
mixin InputRangeStub;
const(typeof(this)) save() const @nogc nothrow pure @safe
{
return this;
}
}
static assert(!isForwardRange!Range3);
}
/**
* Determines whether $(D_PARAM R) is a bidirectional range.
*
* A bidirectional range is a forward range that also defines:
*
* $(UL
* $(LI back)
* $(LI popBack)
* )
*
* Params:
* R = The type to be tested.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM R) is a bidirectional range,
* $(D_KEYWORD false) otherwise.
*
* See_Also: $(D_PSYMBOL isForwardRange).
*/
template isBidirectionalRange(R)
{
static if (is(ReturnType!((R r) => r.back()) U)
&& is(typeof(R.popBack())))
{
enum bool isBidirectionalRange = isForwardRange!R
&& is(U == ReturnType!((R r) => r.front()));
}
else
{
enum bool isBidirectionalRange = isDynamicArrayRange!R;
}
}
///
@nogc nothrow pure @safe unittest
{
static struct Range
{
void popFront() @nogc nothrow pure @safe
{
}
void popBack() @nogc nothrow pure @safe
{
}
@property int front() @nogc nothrow pure @safe
{
return 0;
}
@property int back() @nogc nothrow pure @safe
{
return 0;
}
bool empty() const @nogc nothrow pure @safe
{
return true;
}
Range save() @nogc nothrow pure @safe
{
return this;
}
}
static assert(isBidirectionalRange!Range);
static assert(isBidirectionalRange!(int[]));
static assert(!isBidirectionalRange!(void[]));
}
@nogc nothrow pure @safe unittest
{
static struct Range(T, U)
{
void popFront() @nogc nothrow pure @safe
{
}
void popBack() @nogc nothrow pure @safe
{
}
@property T front() @nogc nothrow pure @safe
{
return T.init;
}
@property U back() @nogc nothrow pure @safe
{
return U.init;
}
bool empty() const @nogc nothrow pure @safe
{
return true;
}
Range save() @nogc nothrow pure @safe
{
return this;
}
}
static assert(!isBidirectionalRange!(Range!(int, uint)));
static assert(!isBidirectionalRange!(Range!(int, const int)));
}
/**
* Determines whether $(D_PARAM R) is a random-access range.
*
* A random-access range is a range that allows random access to its
* elements by index using $(D_INLINECODE [])-operator (defined with
* $(D_INLINECODE opIndex())). Further a random access range should
* have a length or be infinite.
*
* Params:
* R = The type to be tested.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM R) is a random-access range,
* $(D_KEYWORD false) otherwise.
*
* See_Also: $(D_PSYMBOL isInfinite),
* $(D_PSYMBOL hasLength).
*
* Note: This definition differs from `std.range.primitives.isRandomAccessRange`
* in the D standard library in that it does not also require $(D_PARAM R) to
* be a forward range and a bidirectional range. Those properties may be tested
* separately with $(D_PSYMBOL isForwardRange) and
* $(D_PSYMBOL isBidirectionalRange).
*/
template isRandomAccessRange(R)
{
static if (is(ReturnType!((R r) => r.opIndex(size_t.init)) U))
{
enum bool isRandomAccessRange = isInputRange!R
&& (hasLength!R || isInfinite!R)
&& is(U == ReturnType!((R r) => r.front()));
}
else
{
enum bool isRandomAccessRange = isDynamicArrayRange!R;
}
}
///
@nogc nothrow pure @safe unittest
{
static struct A
{
void popFront() @nogc nothrow pure @safe
{
}
void popBack() @nogc nothrow pure @safe
{
}
@property int front() @nogc nothrow pure @safe
{
return 0;
}
@property int back() @nogc nothrow pure @safe
{
return 0;
}
bool empty() const @nogc nothrow pure @safe
{
return true;
}
typeof(this) save() @nogc nothrow pure @safe
{
return this;
}
int opIndex(const size_t pos) @nogc nothrow pure @safe
{
return 0;
}
size_t length() const @nogc nothrow pure @safe
{
return 0;
}
}
static assert(isRandomAccessRange!A);
static assert(isRandomAccessRange!(int[]));
static assert(!isRandomAccessRange!(void[]));
static struct B
{
void popFront() @nogc nothrow pure @safe
{
}
@property int front() @nogc nothrow pure @safe
{
return 0;
}
enum bool empty = false;
typeof(this) save() @nogc nothrow pure @safe
{
return this;
}
int opIndex(const size_t pos) @nogc nothrow pure @safe
{
return 0;
}
}
static assert(isRandomAccessRange!B);
}
@nogc nothrow pure @safe unittest
{
static struct Range1
{
mixin InputRangeStub;
mixin BidirectionalRangeStub;
typeof(this) save() @nogc nothrow pure @safe
{
return this;
}
int opIndex(const size_t pos) @nogc nothrow pure @safe
{
return 0;
}
}
static assert(!isRandomAccessRange!Range1);
static struct Range2(Args...)
{
mixin InputRangeStub;
mixin BidirectionalRangeStub;
typeof(this) save() @nogc nothrow pure @safe
{
return this;
}
int opIndex(Args) @nogc nothrow pure @safe
{
return 0;
}
size_t length() const @nogc nothrow pure @safe
{
return 0;
}
}
static assert(isRandomAccessRange!(Range2!size_t));
static assert(!isRandomAccessRange!(Range2!()));
static assert(!isRandomAccessRange!(Range2!(size_t, size_t)));
static struct Range3
{
mixin InputRangeStub;
mixin BidirectionalRangeStub;
typeof(this) save() @nogc nothrow pure @safe
{
return this;
}
int opIndex(const size_t pos1, const size_t pos2 = 0)
@nogc nothrow pure @safe
{
return 0;
}
size_t length() const @nogc nothrow pure @safe
{
return 0;
}
}
static assert(isRandomAccessRange!Range3);
static struct Range4
{
mixin InputRangeStub;
mixin BidirectionalRangeStub;
typeof(this) save() @nogc nothrow pure @safe
{
return this;
}
int opIndex(const size_t pos1) @nogc nothrow pure @safe
{
return 0;
}
size_t opDollar() const @nogc nothrow pure @safe
{
return 0;
}
}
static assert(!isRandomAccessRange!Range4);
}
/**
* Puts $(D_PARAM e) into the $(D_PARAM range).
*
* $(D_PSYMBOL R) should be an output range for $(D_PARAM E).
*
* $(D_PARAM range) is advanced after putting an element into it if all of the
* following conditions are met:
*
* $(OL
* $(LI $(D_PSYMBOL R) is an input range)
* $(LI $(D_PSYMBOL R) doesn't define a `put`-method)
* $(LI $(D_PARAM e) can be assigned to $(D_INLINECODE range.front))
* )
*
* Params:
* R = Target range type.
* E = Source element type.
* range = Target range.
* e = Source element.
*
* See_Also: $(D_PSYMBOL isOutputRange).
*/
void put(R, E)(ref R range, auto ref E e)
{
static if (__traits(hasMember, R, "put")
&& is(typeof((R r, E e) => r.put(e))))
{
range.put(e);
}
else static if (isInputRange!R
&& is(typeof((R r, E e) => r.front = e)))
{
range.front = e;
range.popFront();
}
else static if (is(typeof((R r, E e) => r(e))))
{
range(e);
}
else static if (isInputRange!E)
{
for (; !e.empty; e.popFront())
{
put(range, e.front);
}
}
else
{
static assert(false, R.stringof ~ " is not an output range for "
~ E.stringof);
}
}
///
@nogc nothrow pure @safe unittest
{
int[2] actual;
auto slice = actual[];
put(slice, 2);
assert(actual == [2, 0]);
}
///
@nogc nothrow pure @safe unittest
{
static struct Put
{
int e;
void put(int e)
{
this.e = e;
}
}
Put p;
put(p, 2);
assert(p.e == 2);
}
///
@nogc nothrow pure @safe unittest
{
static struct OpCall
{
int e;
void opCall(int e)
{
this.e = e;
}
}
OpCall oc;
put(oc, 2);
assert(oc.e == 2);
}
///
@nogc nothrow pure @safe unittest
{
int[2] actual;
int[2] expected = [2, 3];
auto slice = actual[];
put(slice, expected[]);
assert(actual == expected);
}
/**
* Determines whether $(D_PARAM R) is an output range for the elemens of type
* $(D_PARAM E).
*
* If $(D_PARAM R) is an output range for the elements of type $(D_PARAM E)
* if an element `e` of type $(D_PARAM E) can be put into the range instance
* `r` in one of the following ways:
*
* $(TABLE
* $(TR
* $(TH Code)
* $(TH Scenario)
* )
* $(TR
* $(TD r.put(e))
* $(TD $(D_PARAM R) defines `put` for $(D_PARAM E).)
* )
* $(TR
* $(TD r.front = e)
* $(TD $(D_PARAM R) is an input range, whose element type is
* $(D_PARAM E) and `front` is an lvalue.)
* )
* $(TR
* $(TD r(e))
* $(TD $(D_PARAM R) defines `opCall` for $(D_PARAM E).)
* )
* $(TR
* $(TD for (; !e.empty; e.popFront()) r.put(e.front) $(BR)
* for (; !e.empty; e.popFront(), r.popFront())
* r.front = e.front $(BR)
* for (; !e.empty; e.popFront()) r(e.front)
* )
* $(TD $(D_PARAM E) is input range, whose elements can be put into
* $(D_PARAM R) according to the rules described above in this table.
* )
* )
* )
*
* Params:
* R = The type to be tested.
* E = Element type should be tested for.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM R) is an output range for the
* elements of the type $(D_PARAM E), $(D_KEYWORD false) otherwise.
*/
enum bool isOutputRange(R, E) = is(typeof((ref R r, ref E e) => put(r, e)));
///
@nogc nothrow pure @safe unittest
{
static struct R1
{
void put(int) @nogc nothrow pure @safe
{
}
}
static assert(isOutputRange!(R1, int));
static struct R2
{
int value;
void popFront() @nogc nothrow pure @safe
{
}
ref int front() @nogc nothrow pure @safe
{
return value;
}
bool empty() const @nogc nothrow pure @safe
{
return true;
}
}
static assert(isOutputRange!(R2, int));
static struct R3
{
void popFront() @nogc nothrow pure @safe
{
}
int front() @nogc nothrow pure @safe
{
return 0;
}
bool empty() const @nogc nothrow pure @safe
{
return true;
}
}
static assert(!isOutputRange!(R3, int));
static struct R4
{
void opCall(int) @nogc nothrow pure @safe
{
}
}
static assert(isOutputRange!(R4, int));
static assert(isOutputRange!(R1, R3));
static assert(isOutputRange!(R2, R3));
static assert(isOutputRange!(R4, R3));
}
/**
* Determines whether $(D_PARAM R) is an infinite range.
*
* An infinite range is an input range whose `empty` member is defined as
* $(D_KEYWORD enum) which is always $(D_KEYWORD false).
*
* Params:
* R = A type.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM R) is an infinite range,
* $(D_KEYWORD false) otherwise.
*/
template isInfinite(R)
{
static if (isInputRange!R && is(typeof({enum bool e = R.empty;})))
{
enum bool isInfinite = R.empty == false;
}
else
{
enum bool isInfinite = false;
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(!isInfinite!int);
static struct NotRange
{
enum bool empty = false;
}
static assert(!isInfinite!NotRange);
static struct InfiniteRange
{
void popFront() @nogc nothrow pure @safe
{
}
@property int front() @nogc nothrow pure @safe
{
return 0;
}
enum bool empty = false;
}
static assert(isInfinite!InfiniteRange);
static struct InputRange
{
void popFront() @nogc nothrow pure @safe
{
}
@property int front() @nogc nothrow pure @safe
{
return 0;
}
@property bool empty() const @nogc nothrow pure @safe
{
return false;
}
}
static assert(!isInfinite!InputRange);
}
@nogc nothrow pure @safe unittest
{
static struct StaticConstRange
{
void popFront() @nogc nothrow pure @safe
{
}
@property int front() @nogc nothrow pure @safe
{
return 0;
}
static bool empty = false;
}
static assert(!isInfinite!StaticConstRange);
static struct TrueRange
{
void popFront() @nogc nothrow pure @safe
{
}
@property int front() @nogc nothrow pure @safe
{
return 0;
}
static const bool empty = true;
}
static assert(!isInfinite!TrueRange);
}
/**
* Removes exactly $(D_PARAM count) first elements from the input range
* $(D_PARAM range).
*
* $(D_PARAM R) must have length or be infinite.
*
* Params:
* R = Range type.
* range = Some input range.
* count = Number of elements to remove.
*
* See_Also: $(D_PSYMBOL popBackExactly),
* $(D_PSYMBOL popFrontN),
* $(D_PSYMBOL isInputRange),
* $(D_PSYMBOL hasLength),
* $(D_PSYMBOL isInfinite).
*
* Precondition: If $(D_PARAM R) has length, it must be less than or equal to
* $(D_PARAM count).
*/
void popFrontExactly(R)(ref R range, size_t count)
if (isInputRange!R && (hasLength!R || isInfinite!R))
in
{
static if (hasLength!R)
{
assert(count <= range.length);
}
}
do
{
static if (hasSlicing!R)
{
range = range[count .. $];
}
else
{
while (count-- != 0)
{
range.popFront();
}
}
}
///
@nogc nothrow pure @safe unittest
{
int[5] a = [1, 2, 3, 4, 5];
auto slice = a[];
popFrontExactly(slice, 3);
assert(slice.length == 2);
assert(slice[0] == 4);
assert(slice[$ - 1] == 5);
popFrontExactly(slice, 2);
assert(slice.length == 0);
}
/**
* Removes exactly $(D_PARAM count) last elements from the bidirectional range
* $(D_PARAM range).
*
* $(D_PARAM R) must have length or be infinite.
*
* Params:
* R = Range type.
* range = Some bidirectional range.
* count = Number of elements to remove.
*
* See_Also: $(D_PSYMBOL popFrontExactly),
* $(D_PSYMBOL popBackN),
* $(D_PSYMBOL isBidirectionalRange),
* $(D_PSYMBOL hasLength),
* $(D_PSYMBOL isInfinite).
*
* Precondition: If $(D_PARAM R) has length, it must be less than or equal to
* $(D_PARAM count).
*/
void popBackExactly(R)(ref R range, size_t count)
if (isBidirectionalRange!R && (hasLength!R || isInfinite!R))
in
{
static if (hasLength!R)
{
assert(count <= range.length);
}
}
do
{
static if (hasSlicing!R)
{
range = range[0 .. $ - count];
}
else
{
while (count-- != 0)
{
range.popBack();
}
}
}
///
@nogc nothrow pure @safe unittest
{
int[5] a = [1, 2, 3, 4, 5];
auto slice = a[];
popBackExactly(slice, 3);
assert(slice.length == 2);
assert(slice[0] == 1);
assert(slice[$ - 1] == 2);
popBackExactly(slice, 2);
assert(slice.length == 0);
}
/**
* Removes maximum $(D_PARAM count) first elements from the input range
* $(D_PARAM range).
*
* Params:
* R = Range type.
* range = Some input range.
* count = Number of elements to remove.
*
* See_Also: $(D_PSYMBOL popBackN),
* $(D_PSYMBOL popFrontExactly),
* $(D_PSYMBOL isInputRange).
*/
void popFrontN(R)(ref R range, size_t count)
if (isInputRange!R)
{
static if (hasLength!R && hasSlicing!R)
{
range = range[min(count, range.length) .. $];
}
else static if (hasLength!R)
{
size_t length = min(count, range.length);
while (length--)
{
range.popFront();
}
}
else
{
while (count-- != 0 && !range.empty)
{
range.popFront();
}
}
}
///
@nogc nothrow pure @safe unittest
{
int[5] a = [1, 2, 3, 4, 5];
auto slice = a[];
popFrontN(slice, 3);
assert(slice.length == 2);
assert(slice[0] == 4);
assert(slice[$ - 1] == 5);
popFrontN(slice, 20);
assert(slice.length == 0);
}
/**
* Removes maximum $(D_PARAM count) last elements from the bidirectional range
* $(D_PARAM range).
*
* Params:
* R = Range type.
* range = Some bidirectional range.
* count = Number of elements to remove.
*
* See_Also: $(D_PSYMBOL popFrontN),
* $(D_PSYMBOL popBackExactly),
* $(D_PSYMBOL isBidirectionalRange).
*/
void popBackN(R)(ref R range, size_t count)
if (isBidirectionalRange!R)
{
static if (hasLength!R && hasSlicing!R)
{
range = range[0 .. $ - min(count, range.length)];
}
else static if (hasLength!R)
{
size_t length = min(count, range.length);
while (length--)
{
range.popBack();
}
}
else
{
while (count-- != 0 && !range.empty)
{
range.popBack();
}
}
}
///
@nogc nothrow pure @safe unittest
{
int[5] a = [1, 2, 3, 4, 5];
auto slice = a[];
popBackN(slice, 3);
assert(slice.length == 2);
assert(slice[0] == 1);
assert(slice[$ - 1] == 2);
popBackN(slice, 20);
assert(slice.length == 0);
}
@nogc nothrow pure @safe unittest
{
static struct InfiniteRange
{
private int i;
InfiniteRange save() @nogc nothrow pure @safe
{
return this;
}
void popFront() @nogc nothrow pure @safe
{
++this.i;
}
void popBack() @nogc nothrow pure @safe
{
--this.i;
}
@property int front() const @nogc nothrow pure @safe
{
return this.i;
}
@property int back() const @nogc nothrow pure @safe
{
return this.i;
}
enum bool empty = false;
}
{
InfiniteRange range;
popFrontExactly(range, 2);
assert(range.front == 2);
popFrontN(range, 2);
assert(range.front == 4);
}
{
InfiniteRange range;
popBackExactly(range, 2);
assert(range.back == -2);
popBackN(range, 2);
assert(range.back == -4);
}
}
@nogc nothrow pure @safe unittest
{
static struct Range
{
private int[5] a = [1, 2, 3, 4, 5];
private size_t begin = 0, end = 5;
Range save() @nogc nothrow pure @safe
{
return this;
}
void popFront() @nogc nothrow pure @safe
{
++this.begin;
}
void popBack() @nogc nothrow pure @safe
{
--this.end;
}
@property int front() const @nogc nothrow pure @safe
{
return this.a[this.begin];
}
@property int back() const @nogc nothrow pure @safe
{
return this.a[this.end - 1];
}
@property bool empty() const @nogc nothrow pure @safe
{
return this.begin >= this.end;
}
}
{
Range range;
popFrontN(range, 3);
assert(range.front == 4);
assert(range.back == 5);
popFrontN(range, 20);
assert(range.empty);
}
{
Range range;
popBackN(range, 3);
assert(range.front == 1);
assert(range.back == 2);
popBackN(range, 20);
assert(range.empty);
}
}
/**
* Moves the front element of an input range.
*
* The front element is left in a valid but unspecified state.
* $(D_PSYMBOL moveFront) doesn't advances the range, so `popFront` should be
* probably called after this function.
*
* Params:
* R = Type of the range.
* range = Input range.
*
* Returns: The front element of the $(D_PSYMBOL range).
*
* See_Also: $(D_PSYMBOL move).
*/
ElementType!R moveFront(R)(R range)
if (isInputRange!R)
{
static if (!hasElaborateCopyConstructor!(ElementType!R))
{
return range.front;
}
else static if (is(typeof(((ref ElementType!R e) => e)(range.front))))
{
return move(range.front);
}
else
{
static assert(false, "Front element cannot be moved");
}
}
///
@nogc nothrow pure @safe unittest
{
// Has elements without a postblit constructor.
int[2] a = 5;
assert(moveFront(a[]) == 5);
}
@nogc nothrow pure @safe unittest
{
static struct Element
{
this(this) @nogc nothrow pure @safe
{
assert(false);
}
}
// Returns its elements by reference.
static struct R1
{
Element element;
enum bool empty = false;
ref Element front() @nogc nothrow pure @safe
{
return element;
}
void popFront() @nogc nothrow pure @safe
{
}
}
static assert(is(typeof(moveFront(R1()))));
// Returns elements with a postblit constructor by value. moveFront fails.
static struct R2
{
enum bool empty = false;
Element front() @nogc nothrow pure @safe
{
return Element();
}
void popFront() @nogc nothrow pure @safe
{
}
}
static assert(!is(typeof(moveFront(R2()))));
}
/**
* Moves the back element of a bidirectional range.
*
* The back element is left in a valid but unspecified state.
* $(D_PSYMBOL moveBack) doesn't advances the range, so `popBack` should be
* probably called after this function.
*
* Params:
* R = Type of the range.
* range = Bidirectional range.
*
* Returns: The back element of the $(D_PSYMBOL range).
*
* See_Also: $(D_PSYMBOL move).
*/
ElementType!R moveBack(R)(R range)
if (isBidirectionalRange!R)
{
static if (!hasElaborateCopyConstructor!(ElementType!R))
{
return range.back;
}
else static if (is(typeof(((ref ElementType!R e) => e)(range.back))))
{
return move(range.back);
}
else
{
static assert(false, "Back element cannot be moved");
}
}
///
@nogc nothrow pure @safe unittest
{
// Has elements without a postblit constructor.
int[2] a = 5;
assert(moveBack(a[]) == 5);
}
@nogc nothrow pure @safe unittest
{
static struct Element
{
this(this) @nogc nothrow pure @safe
{
assert(false);
}
}
// Returns its elements by reference.
static struct R1
{
Element element;
enum bool empty = false;
ref Element back() @nogc nothrow pure @safe
{
return element;
}
alias front = back;
void popBack() @nogc nothrow pure @safe
{
}
alias popFront = popBack;
R1 save() @nogc nothrow pure @safe
{
return this;
}
}
static assert(is(typeof(moveBack(R1()))));
// Returns elements with a postblit constructor by value. moveBack fails.
static struct R2
{
enum bool empty = false;
Element back() @nogc nothrow pure @safe
{
return Element();
}
alias front = back;
void popBack() @nogc nothrow pure @safe
{
}
alias popFront = popBack;
R2 save() @nogc nothrow pure @safe
{
return this;
}
}
static assert(!is(typeof(moveBack(R2()))));
}
/**
* Moves the element at the position $(D_PARAM n) out of the range.
*
* The moved element is left in a valid but unspecified state.
*
* Params:
* R = Range type.
* range = Random-access range.
* n = Element position.
*
* Returns: The element at the position $(D_PARAM n).
*
* See_Also: $(D_PSYMBOL move).
*/
ElementType!R moveAt(R)(R range, size_t n)
if (isRandomAccessRange!R)
{
static if (!hasElaborateCopyConstructor!(ElementType!R))
{
return range[n];
}
else static if (is(typeof(((ref ElementType!R e) => e)(range[0]))))
{
return move(range[n]);
}
else
{
static assert(false, "Random element cannot be moved");
}
}
///
@nogc nothrow pure @safe unittest
{
// Has elements without a postblit constructor.
int[3] a = 5;
assert(moveAt(a[], 1) == 5);
}
@nogc nothrow pure @safe unittest
{
static struct Element
{
this(this) @nogc nothrow pure @safe
{
assert(false);
}
}
// Returns its elements by reference.
static struct R1
{
Element element;
enum bool empty = false;
ref Element front() @nogc nothrow pure @safe
{
return element;
}
void popFront() @nogc nothrow pure @safe
{
}
ref Element opIndex(size_t)
{
return element;
}
}
static assert(is(typeof(moveAt(R1(), 0))));
// Returns elements with a postblit constructor by value. moveAt fails.
static struct R2
{
enum bool empty = false;
Element front() @nogc nothrow pure @safe
{
return Element();
}
void popFront() @nogc nothrow pure @safe
{
}
Element opIndex() @nogc nothrow pure @safe
{
return Element();
}
}
static assert(!is(typeof(moveAt(R2(), 0))));
}
/**
* Determines whether $(D_PSYMBOL R) is a range containing mobile elements,
* i.e. elements that can be moved out of the range.
*
* Having mobile elements means for an input range to support
* $(D_PSYMBOL moveFront), for a bidirectional range - both,
* $(D_PSYMBOL moveFront) and $(D_PSYMBOL moveBack), for a random-access
* range - $(D_PSYMBOL moveFront) and $(D_PSYMBOL moveAt).
*
* Params:
* R = Range type.
*
* Returns: $(D_KEYWORD true) if $(D_PSYMBOL R) has mobile elements,
* $(D_KEYWORD false) otherwise.
*
* See_Also: $(D_PSYMBOL moveFront), $(D_PSYMBOL moveBack),
* $(D_PSYMBOL moveAt).
*/
template hasMobileElements(R)
{
static if (isRandomAccessRange!R)
{
enum bool hasMobileElements = is(typeof((R r) => moveFront(r)))
&& is(typeof((R r) => moveAt(r, 0)));
}
else static if (isBidirectionalRange!R)
{
enum bool hasMobileElements = is(typeof((R r) => moveFront(r)))
&& is(typeof((R r) => moveBack(r)));
}
else
{
enum bool hasMobileElements = is(typeof((R r) => moveFront(r)));
}
}
///
@nogc nothrow pure @safe unittest
{
static assert(hasMobileElements!(int[]));
}
///
@nogc nothrow pure @safe unittest
{
static struct Element
{
this(this) @nogc nothrow pure @safe
{
}
}
static struct R1
{
enum bool empty = false;
Element front() @nogc nothrow pure @safe
{
return Element();
}
void popFront() @nogc nothrow pure @safe
{
}
}
static assert(!hasMobileElements!R1);
static struct R2
{
enum bool empty = false;
private Element front_;
ref Element front() @nogc nothrow pure @safe
{
return front_;
}
void popFront() @nogc nothrow pure @safe
{
}
}
static assert(hasMobileElements!R2);
}
/**
* Determines whether $(D_PARAM R) provides access to its elements by
* reference.
*
* Params:
* R = Range type.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM R) has lvalue elements,
* $(D_KEYWORD false) otherwise.
*/
template hasLvalueElements(R)
{
private alias refDg = (ref ElementType!R e) => e;
static if (isRandomAccessRange!R)
{
enum bool hasLvalueElements = is(typeof(refDg(R.init.front)))
&& is(typeof(refDg(R.init[0])));
}
else static if (isBidirectionalRange!R)
{
enum bool hasLvalueElements = is(typeof(refDg(R.init.front)))
&& is(typeof(refDg(R.init.back)));
}
else
{
enum bool hasLvalueElements = is(typeof(refDg(R.init.front)));
}
}
///
@nogc nothrow pure @safe unittest
{
static struct R1
{
enum bool empty = false;
int front() @nogc nothrow pure @safe
{
return 5;
}
void popFront() @nogc nothrow pure @safe
{
}
}
static assert(!hasLvalueElements!R1);
static struct R2
{
int element;
enum bool empty = false;
ref const(int) front() const @nogc nothrow pure @safe
{
return element;
}
void popFront() @nogc nothrow pure @safe
{
}
ref const(int) opIndex(size_t) const @nogc nothrow pure @safe
{
return element;
}
}
static assert(hasLvalueElements!R2);
}
/**
* Determines whether the elements of $(D_PARAM R) are assignable.
*
* Params:
* R = Range type.
*
* Returns: $(D_KEYWORD true) if the elements of $(D_PARAM R) are assignable
* $(D_KEYWORD false) otherwise.
*/
template hasAssignableElements(R)
{
static if (isRandomAccessRange!R)
{
enum bool assignable = is(typeof({R.init.front = R.init.front;}))
&& is(typeof({R.init[0] = R.init[0];}));
}
else static if (isBidirectionalRange!R)
{
enum bool assignable = is(typeof({R.init.front = R.init.front;}))
&& is(typeof({R.init.back = R.init.back;}));
}
else
{
enum bool assignable = is(typeof({R.init.front = R.init.front;}));
}
enum bool hasAssignableElements = assignable;
}
///
@nogc nothrow pure @safe unittest
{
static struct R1
{
int element;
enum bool empty = false;
ref int front() @nogc nothrow pure @safe
{
return element;
}
alias back = front;
void popFront() @nogc nothrow pure @safe
{
}
alias popBack = popFront;
R1 save() const @nogc nothrow pure @safe
{
return this;
}
}
static assert(hasAssignableElements!R1);
static struct R2
{
int element;
enum bool empty = false;
ref const(int) front() const @nogc nothrow pure @safe
{
return element;
}
alias back = front;
void popFront() @nogc nothrow pure @safe
{
}
alias popBack = popFront;
R2 save() const @nogc nothrow pure @safe
{
return this;
}
}
static assert(!hasAssignableElements!R2);
}
/**
* Determines whether the elements of $(D_PSYMBOL R) can be swapped with
* $(D_PSYMBOL swap).
*
* Params:
* R = Range type.
*
* Returns: $(D_KEYWORD true) if $(D_PARAM R) has swappable elements,
* $(D_KEYWORD false) otherwise.
*/
template hasSwappableElements(R)
{
static if (isRandomAccessRange!R)
{
enum bool hasSwappableElements = is(typeof(swap(R.init.front, R.init.front)))
&& is(typeof(swap(R.init[0], R.init[0])));
}
else static if (isBidirectionalRange!R)
{
enum bool hasSwappableElements = is(typeof(swap(R.init.front, R.init.front)))
&& is(typeof(swap(R.init.back, R.init.back)));
}
else
{
enum bool hasSwappableElements = is(typeof(swap(R.init.front, R.init.front)));
}
}
///
@nogc nothrow pure @safe unittest
{
static struct R1
{
int element;
enum bool empty = false;
ref int front() @nogc nothrow pure @safe
{
return element;
}
alias back = front;
void popFront() @nogc nothrow pure @safe
{
}
alias popBack = popFront;
R1 save() const @nogc nothrow pure @safe
{
return this;
}
}
static assert(hasSwappableElements!R1);
static struct R2
{
int element;
enum bool empty = false;
int front() const @nogc nothrow pure @safe
{
return element;
}
alias back = front;
void popFront() @nogc nothrow pure @safe
{
}
alias popBack = popFront;
R2 save() const @nogc nothrow pure @safe
{
return this;
}
}
static assert(!hasSwappableElements!R2);
}