mirror of
https://github.com/dlang/phobos.git
synced 2025-05-01 23:50:31 +03:00
1210 lines
33 KiB
D
1210 lines
33 KiB
D
/**
|
|
$(SCRIPT inhibitQuickIndex = 1;)
|
|
|
|
This is a submodule of $(MREF std, experimental, ndslice).
|
|
|
|
Operators only change strides and lengths of a slice.
|
|
The range of a slice remains unmodified.
|
|
All operators return slice of the same type as the type of the argument.
|
|
|
|
$(BOOKTABLE $(H2 Transpose operators),
|
|
|
|
$(TR $(TH Function Name) $(TH Description))
|
|
$(T2 transposed, Permutes dimensions. $(BR)
|
|
`iotaSlice(3, 4, 5, 6, 7).transposed!(4, 0, 1).shape` returns `[7, 3, 4, 5, 6]`.)
|
|
$(T2 swapped, Swaps dimensions $(BR)
|
|
`iotaSlice(3, 4, 5).swapped!(1, 2).shape` returns `[3, 5, 4]`.)
|
|
$(T2 everted, Reverses the order of dimensions $(BR)
|
|
`iotaSlice(3, 4, 5).everted.shape` returns `[5, 4, 3]`.)
|
|
)
|
|
See also $(SUBREF selection, evertPack).
|
|
|
|
$(BOOKTABLE $(H2 Iteration operators),
|
|
|
|
$(TR $(TH Function Name) $(TH Description))
|
|
$(T2 strided, Multiplies the stride of a selected dimension by a factor.$(BR)
|
|
`iotaSlice(13, 40).strided!(0, 1)(2, 5).shape` equals to `[7, 8]`.)
|
|
$(T2 reversed, Reverses the direction of iteration for selected dimensions. $(BR)
|
|
`slice.reversed!0` returns the slice with reversed direction of iteration for top level dimension.)
|
|
$(T2 allReversed, Reverses the direction of iteration for all dimensions. $(BR)
|
|
`iotaSlice(4, 5).allReversed` equals to `20.iota.retro.sliced(4, 5)`.)
|
|
)
|
|
|
|
$(BOOKTABLE $(H2 Other operators),
|
|
$(TR $(TH Function Name) $(TH Description))
|
|
$(T2 rotated, Rotates two selected dimensions by `k*90` degrees. $(BR)
|
|
`iotaSlice(2, 3).rotated` equals to `[[2, 5], [1, 4], [0, 3]]`.)
|
|
)
|
|
|
|
$(H4 Drop operators)
|
|
|
|
$(LREF dropToHypercube)
|
|
$(LREF drop) $(LREF dropBack)
|
|
$(LREF dropOne) $(LREF dropBackOne)
|
|
$(LREF dropExactly) $(LREF dropBackExactly)
|
|
$(LREF allDrop) $(LREF allDropBack)
|
|
$(LREF allDropOne) $(LREF allDropBackOne)
|
|
$(LREF allDropExactly) $(LREF allDropBackExactly)
|
|
|
|
$(GRAMMAR
|
|
$(GNAME DropOperatorName):
|
|
$(D dropToHypercube)
|
|
$(GLINK DropRoot)
|
|
$(GLINK DropRoot) $(GLINK DropSuffix)
|
|
$(GLINK DropRoot) $(D Back)
|
|
$(GLINK DropRoot) $(D Back) $(GLINK DropSuffix)
|
|
$(GNAME DropRoot):
|
|
$(D drop)
|
|
$(D allDrop)
|
|
$(GNAME DropSuffix):
|
|
$(D One)
|
|
$(D Exactly)
|
|
)
|
|
|
|
$(H2 Bifacial operators)
|
|
|
|
Some operators are bifacial,
|
|
i.e. they have two versions: one with template parameters, and another one
|
|
with function parameters. Versions with template parameters are preferable
|
|
because they allow compile time checks and can be optimized better.
|
|
|
|
$(BOOKTABLE ,
|
|
|
|
$(TR $(TH Function Name) $(TH Variadic) $(TH Template) $(TH Function))
|
|
$(T4 swapped, No, `slice.swapped!(2, 3)`, `slice.swapped(2, 3)`)
|
|
$(T4 rotated, No, `slice.rotated!(2, 3)(-1)`, `slice.rotated(2, 3, -1)`)
|
|
$(T4 strided, Yes/No, `slice.strided!(1, 2)(20, 40)`, `slice.strided(1, 20).strided(2, 40)`)
|
|
$(T4 transposed, Yes, `slice.transposed!(1, 4, 3)`, `slice.transposed(1, 4, 3)`)
|
|
$(T4 reversed, Yes, `slice.reversed!(0, 2)`, `slice.reversed(0, 2)`)
|
|
)
|
|
|
|
Bifacial interface of $(LREF drop), $(LREF dropBack)
|
|
$(LREF dropExactly), and $(LREF dropBackExactly)
|
|
is identical to that of $(LREF strided).
|
|
|
|
Bifacial interface of $(LREF dropOne) and $(LREF dropBackOne)
|
|
is identical to that of $(LREF reversed).
|
|
|
|
License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
|
|
|
Authors: Ilya Yaroshenko
|
|
|
|
Source: $(PHOBOSSRC std/_experimental/_ndslice/_iteration.d)
|
|
|
|
Macros:
|
|
SUBREF = $(REF_ALTTEXT $(TT $2), $2, std,experimental, ndslice, $1)$(NBSP)
|
|
T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
|
|
T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4))
|
|
*/
|
|
/// @@@DEPRECATED_2017-04@@@
|
|
deprecated("Please use mir-algorithm DUB package: http://github.com/libmir/mir-algorithm")
|
|
module std.experimental.ndslice.iteration;
|
|
|
|
import std.traits;
|
|
import std.meta;
|
|
|
|
import std.experimental.ndslice.internal;
|
|
import std.experimental.ndslice.slice; //: Slice;
|
|
|
|
@fmb:
|
|
|
|
private enum _swappedCode = q{
|
|
with (slice)
|
|
{
|
|
auto tl = _lengths[dimensionA];
|
|
auto ts = _strides[dimensionA];
|
|
_lengths[dimensionA] = _lengths[dimensionB];
|
|
_strides[dimensionA] = _strides[dimensionB];
|
|
_lengths[dimensionB] = tl;
|
|
_strides[dimensionB] = ts;
|
|
}
|
|
return slice;
|
|
};
|
|
|
|
/++
|
|
Swaps two dimensions.
|
|
|
|
Params:
|
|
slice = input slice
|
|
dimensionA = first dimension
|
|
dimensionB = second dimension
|
|
Returns:
|
|
n-dimensional slice of the same type
|
|
See_also: $(LREF everted), $(LREF transposed)
|
|
+/
|
|
template swapped(size_t dimensionA, size_t dimensionB)
|
|
{
|
|
@fmb auto swapped(size_t N, Range)(Slice!(N, Range) slice)
|
|
{
|
|
{
|
|
enum i = 0;
|
|
alias dimension = dimensionA;
|
|
mixin DimensionCTError;
|
|
}
|
|
{
|
|
enum i = 1;
|
|
alias dimension = dimensionB;
|
|
mixin DimensionCTError;
|
|
}
|
|
mixin (_swappedCode);
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
Slice!(N, Range) swapped(size_t N, Range)(Slice!(N, Range) slice, size_t dimensionA, size_t dimensionB)
|
|
in{
|
|
{
|
|
alias dimension = dimensionA;
|
|
mixin (DimensionRTError);
|
|
}
|
|
{
|
|
alias dimension = dimensionB;
|
|
mixin (DimensionRTError);
|
|
}
|
|
}
|
|
body
|
|
{
|
|
mixin (_swappedCode);
|
|
}
|
|
|
|
/// ditto
|
|
Slice!(2, Range) swapped(Range)(Slice!(2, Range) slice)
|
|
body
|
|
{
|
|
return slice.swapped!(0, 1);
|
|
}
|
|
|
|
/// Template
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
assert(iotaSlice(3, 4, 5, 6)
|
|
.swapped!(3, 1)
|
|
.shape == cast(size_t[4])[3, 6, 5, 4]);
|
|
}
|
|
|
|
/// Function
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
assert(iotaSlice(3, 4, 5, 6)
|
|
.swapped(1, 3)
|
|
.shape == cast(size_t[4])[3, 6, 5, 4]);
|
|
}
|
|
|
|
/// 2D
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
assert(iotaSlice(3, 4)
|
|
.swapped
|
|
.shape == cast(size_t[2])[4, 3]);
|
|
}
|
|
|
|
private enum _rotatedCode = q{
|
|
k &= 0b11;
|
|
if (k == 0)
|
|
return slice;
|
|
if (k == 2)
|
|
return slice.allReversed;
|
|
static if (__traits(compiles, { enum _enum = dimensionA + dimensionB; }))
|
|
{
|
|
slice = slice.swapped!(dimensionA, dimensionB);
|
|
if (k == 1)
|
|
return slice.reversed!dimensionA;
|
|
else
|
|
return slice.reversed!dimensionB;
|
|
}
|
|
else
|
|
{
|
|
slice = slice.swapped (dimensionA, dimensionB);
|
|
if (k == 1)
|
|
return slice.reversed(dimensionA);
|
|
else
|
|
return slice.reversed(dimensionB);
|
|
}
|
|
};
|
|
|
|
/++
|
|
Rotates two selected dimensions by `k*90` degrees.
|
|
The order of dimensions is important.
|
|
If the slice has two dimensions, the default direction is counterclockwise.
|
|
|
|
Params:
|
|
slice = input slice
|
|
dimensionA = first dimension
|
|
dimensionB = second dimension
|
|
k = rotation counter, can be negative
|
|
Returns:
|
|
n-dimensional slice of the same type
|
|
+/
|
|
template rotated(size_t dimensionA, size_t dimensionB)
|
|
{
|
|
@fmb auto rotated(size_t N, Range)(Slice!(N, Range) slice, sizediff_t k = 1)
|
|
{
|
|
{
|
|
enum i = 0;
|
|
alias dimension = dimensionA;
|
|
mixin DimensionCTError;
|
|
}
|
|
{
|
|
enum i = 1;
|
|
alias dimension = dimensionB;
|
|
mixin DimensionCTError;
|
|
}
|
|
mixin (_rotatedCode);
|
|
}
|
|
}
|
|
|
|
/// ditto
|
|
Slice!(N, Range) rotated(size_t N, Range)(Slice!(N, Range) slice,
|
|
size_t dimensionA, size_t dimensionB, sizediff_t k = 1)
|
|
in{
|
|
{
|
|
alias dimension = dimensionA;
|
|
mixin (DimensionRTError);
|
|
}
|
|
{
|
|
alias dimension = dimensionB;
|
|
mixin (DimensionRTError);
|
|
}
|
|
}
|
|
body
|
|
{
|
|
mixin (_rotatedCode);
|
|
}
|
|
|
|
/// ditto
|
|
Slice!(2, Range) rotated(Range)(Slice!(2, Range) slice, sizediff_t k = 1)
|
|
body
|
|
{
|
|
return slice.rotated!(0, 1)(k);
|
|
}
|
|
|
|
/// Template
|
|
@safe pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
auto slice = iotaSlice(2, 3);
|
|
|
|
auto a = [[0, 1, 2],
|
|
[3, 4, 5]];
|
|
|
|
auto b = [[2, 5],
|
|
[1, 4],
|
|
[0, 3]];
|
|
|
|
auto c = [[5, 4, 3],
|
|
[2, 1, 0]];
|
|
|
|
auto d = [[3, 0],
|
|
[4, 1],
|
|
[5, 2]];
|
|
|
|
assert(slice.rotated ( 4) == a);
|
|
assert(slice.rotated!(0, 1)(-4) == a);
|
|
assert(slice.rotated (1, 0, 8) == a);
|
|
|
|
assert(slice.rotated == b);
|
|
assert(slice.rotated!(0, 1)(-3) == b);
|
|
assert(slice.rotated (1, 0, 3) == b);
|
|
|
|
assert(slice.rotated ( 6) == c);
|
|
assert(slice.rotated!(0, 1)( 2) == c);
|
|
assert(slice.rotated (0, 1, -2) == c);
|
|
|
|
assert(slice.rotated ( 7) == d);
|
|
assert(slice.rotated!(0, 1)( 3) == d);
|
|
assert(slice.rotated (1, 0, ) == d);
|
|
}
|
|
|
|
/++
|
|
Reverses the order of dimensions.
|
|
|
|
Params:
|
|
slice = input slice
|
|
Returns:
|
|
n-dimensional slice of the same type
|
|
See_also: $(LREF swapped), $(LREF transposed)
|
|
+/
|
|
Slice!(N, Range) everted(size_t N, Range)(Slice!(N, Range) slice)
|
|
{
|
|
mixin _DefineRet;
|
|
with (slice)
|
|
{
|
|
foreach (i; Iota!(0, N))
|
|
{
|
|
ret._lengths[N - 1 - i] = _lengths[i];
|
|
ret._strides[N - 1 - i] = _strides[i];
|
|
}
|
|
foreach (i; Iota!(N, PureN))
|
|
{
|
|
ret._lengths[i] = _lengths[i];
|
|
ret._strides[i] = _strides[i];
|
|
}
|
|
ret._ptr = _ptr;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
assert(iotaSlice(3, 4, 5)
|
|
.everted
|
|
.shape == cast(size_t[3])[5, 4, 3]);
|
|
}
|
|
|
|
private enum _transposedCode = q{
|
|
mixin _DefineRet;
|
|
with (slice)
|
|
{
|
|
foreach (i; Iota!(0, N))
|
|
{
|
|
ret._lengths[i] = _lengths[perm[i]];
|
|
ret._strides[i] = _strides[perm[i]];
|
|
}
|
|
foreach (i; Iota!(N, PureN))
|
|
{
|
|
ret._lengths[i] = _lengths[i];
|
|
ret._strides[i] = _strides[i];
|
|
}
|
|
ret._ptr = _ptr;
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
private size_t[N] completeTranspose(size_t N)(size_t[] dimensions)
|
|
{
|
|
assert(dimensions.length <= N);
|
|
size_t[N] ctr;
|
|
uint[N] mask;
|
|
foreach (i, ref dimension; dimensions)
|
|
{
|
|
mask[dimension] = true;
|
|
ctr[i] = dimension;
|
|
}
|
|
size_t j = dimensions.length;
|
|
foreach (i, e; mask)
|
|
if (e == false)
|
|
ctr[j++] = i;
|
|
return ctr;
|
|
}
|
|
|
|
/++
|
|
N-dimensional transpose operator.
|
|
Brings selected dimensions to the first position.
|
|
Params:
|
|
slice = input slice
|
|
Dimensions = indexes of dimensions to be brought to the first position
|
|
dimensions = indexes of dimensions to be brought to the first position
|
|
dimension = index of dimension to be brought to the first position
|
|
Returns:
|
|
n-dimensional slice of the same type
|
|
See_also: $(LREF swapped), $(LREF everted)
|
|
+/
|
|
template transposed(Dimensions...)
|
|
if (Dimensions.length)
|
|
{
|
|
static if (!allSatisfy!(isSize_t, Dimensions))
|
|
alias transposed = .transposed!(staticMap!(toSize_t, Dimensions));
|
|
else
|
|
@fmb Slice!(N, Range) transposed(size_t N, Range)(Slice!(N, Range) slice)
|
|
{
|
|
mixin DimensionsCountCTError;
|
|
foreach (i, dimension; Dimensions)
|
|
mixin DimensionCTError;
|
|
static assert(isValidPartialPermutation!N([Dimensions]),
|
|
"Failed to complete permutation of dimensions " ~ Dimensions.stringof
|
|
~ tailErrorMessage!());
|
|
enum perm = completeTranspose!N([Dimensions]);
|
|
static assert(perm.isPermutation, __PRETTY_FUNCTION__ ~ ": internal error.");
|
|
mixin (_transposedCode);
|
|
}
|
|
}
|
|
|
|
///ditto
|
|
Slice!(N, Range) transposed(size_t N, Range, size_t M)(Slice!(N, Range) slice, size_t[M] dimensions...)
|
|
in
|
|
{
|
|
mixin (DimensionsCountRTError);
|
|
foreach (dimension; dimensions)
|
|
mixin (DimensionRTError);
|
|
}
|
|
body
|
|
{
|
|
assert(dimensions.isValidPartialPermutation!N,
|
|
"Failed to complete permutation of dimensions."
|
|
~ tailErrorMessage!());
|
|
immutable perm = completeTranspose!N(dimensions);
|
|
assert(perm.isPermutation, __PRETTY_FUNCTION__ ~ ": internal error.");
|
|
mixin (_transposedCode);
|
|
}
|
|
|
|
///ditto
|
|
Slice!(2, Range) transposed(Range)(Slice!(2, Range) slice)
|
|
{
|
|
return .transposed!(1, 0)(slice);
|
|
}
|
|
|
|
/// Template
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
assert(iotaSlice(3, 4, 5, 6, 7)
|
|
.transposed!(4, 1, 0)
|
|
.shape == cast(size_t[5])[7, 4, 3, 5, 6]);
|
|
}
|
|
|
|
/// Function
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
assert(iotaSlice(3, 4, 5, 6, 7)
|
|
.transposed(4, 1, 0)
|
|
.shape == cast(size_t[5])[7, 4, 3, 5, 6]);
|
|
}
|
|
|
|
/// Single-argument function
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
assert(iotaSlice(3, 4, 5, 6, 7)
|
|
.transposed(4)
|
|
.shape == cast(size_t[5])[7, 3, 4, 5, 6]);
|
|
}
|
|
|
|
/// _2-dimensional transpose
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
assert(iotaSlice(3, 4)
|
|
.transposed
|
|
.shape == cast(size_t[2])[4, 3]);
|
|
}
|
|
|
|
private enum _reversedCode = q{
|
|
with (slice)
|
|
{
|
|
if (_lengths[dimension])
|
|
_ptr += _strides[dimension] * (_lengths[dimension] - 1);
|
|
_strides[dimension] = -_strides[dimension];
|
|
}
|
|
};
|
|
|
|
/++
|
|
Reverses the direction of iteration for all dimensions.
|
|
Params:
|
|
slice = input slice
|
|
Returns:
|
|
n-dimensional slice of the same type
|
|
+/
|
|
Slice!(N, Range) allReversed(size_t N, Range)(Slice!(N, Range) slice)
|
|
{
|
|
foreach (dimension; Iota!(0, N))
|
|
{
|
|
mixin (_reversedCode);
|
|
}
|
|
return slice;
|
|
}
|
|
|
|
///
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.range : iota, retro;
|
|
auto a = 20.iota.sliced(4, 5).allReversed;
|
|
auto b = 20.iota.retro.sliced(4, 5);
|
|
assert(a == b);
|
|
}
|
|
|
|
/++
|
|
Reverses the direction of iteration for selected dimensions.
|
|
|
|
Params:
|
|
slice = input slice
|
|
Dimensions = indexes of dimensions to reverse order of iteration
|
|
dimensions = indexes of dimensions to reverse order of iteration
|
|
dimension = index of dimension to reverse order of iteration
|
|
Returns:
|
|
n-dimensional slice of the same type
|
|
+/
|
|
template reversed(Dimensions...)
|
|
if (Dimensions.length)
|
|
{
|
|
static if (!allSatisfy!(isSize_t, Dimensions))
|
|
alias reversed = .reversed!(staticMap!(toSize_t, Dimensions));
|
|
else
|
|
@fmb auto reversed(size_t N, Range)(Slice!(N, Range) slice)
|
|
{
|
|
foreach (i, dimension; Dimensions)
|
|
{
|
|
mixin DimensionCTError;
|
|
mixin (_reversedCode);
|
|
}
|
|
return slice;
|
|
}
|
|
}
|
|
|
|
///ditto
|
|
Slice!(N, Range) reversed(size_t N, Range, size_t M)(Slice!(N, Range) slice, size_t[M] dimensions...)
|
|
in
|
|
{
|
|
foreach (dimension; dimensions)
|
|
mixin (DimensionRTError);
|
|
}
|
|
body
|
|
{
|
|
foreach (i; Iota!(0, M))
|
|
{
|
|
auto dimension = dimensions[i];
|
|
mixin (_reversedCode);
|
|
}
|
|
return slice;
|
|
}
|
|
|
|
///
|
|
pure nothrow @system unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
auto slice = [1, 2, 3, 4].sliced(2, 2);
|
|
assert(slice == [[1, 2], [3, 4]]);
|
|
|
|
// Template
|
|
assert(slice.reversed! 0 == [[3, 4], [1, 2]]);
|
|
assert(slice.reversed! 1 == [[2, 1], [4, 3]]);
|
|
assert(slice.reversed!(0, 1) == [[4, 3], [2, 1]]);
|
|
assert(slice.reversed!(1, 0) == [[4, 3], [2, 1]]);
|
|
assert(slice.reversed!(1, 1) == [[1, 2], [3, 4]]);
|
|
assert(slice.reversed!(0, 0, 0) == [[3, 4], [1, 2]]);
|
|
|
|
// Function
|
|
assert(slice.reversed (0) == [[3, 4], [1, 2]]);
|
|
assert(slice.reversed (1) == [[2, 1], [4, 3]]);
|
|
assert(slice.reversed (0, 1) == [[4, 3], [2, 1]]);
|
|
assert(slice.reversed (1, 0) == [[4, 3], [2, 1]]);
|
|
assert(slice.reversed (1, 1) == [[1, 2], [3, 4]]);
|
|
assert(slice.reversed (0, 0, 0) == [[3, 4], [1, 2]]);
|
|
}
|
|
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection;
|
|
import std.algorithm.comparison : equal;
|
|
import std.range : iota, retro, chain;
|
|
auto i0 = iota(0, 4); auto r0 = i0.retro;
|
|
auto i1 = iota(4, 8); auto r1 = i1.retro;
|
|
auto i2 = iota(8, 12); auto r2 = i2.retro;
|
|
auto slice = 12.iota.sliced(3, 4);
|
|
assert(slice .byElement.equal(chain(i0, i1, i2)));
|
|
// Template
|
|
assert(slice.reversed!(0) .byElement.equal(chain(i2, i1, i0)));
|
|
assert(slice.reversed!(1) .byElement.equal(chain(r0, r1, r2)));
|
|
assert(slice.reversed!(0, 1) .byElement.equal(chain(r2, r1, r0)));
|
|
assert(slice.reversed!(1, 0) .byElement.equal(chain(r2, r1, r0)));
|
|
assert(slice.reversed!(1, 1) .byElement.equal(chain(i0, i1, i2)));
|
|
assert(slice.reversed!(0, 0, 0).byElement.equal(chain(i2, i1, i0)));
|
|
// Function
|
|
assert(slice.reversed (0) .byElement.equal(chain(i2, i1, i0)));
|
|
assert(slice.reversed (1) .byElement.equal(chain(r0, r1, r2)));
|
|
assert(slice.reversed (0, 1) .byElement.equal(chain(r2, r1, r0)));
|
|
assert(slice.reversed (1, 0) .byElement.equal(chain(r2, r1, r0)));
|
|
assert(slice.reversed (1, 1) .byElement.equal(chain(i0, i1, i2)));
|
|
assert(slice.reversed (0, 0, 0).byElement.equal(chain(i2, i1, i0)));
|
|
}
|
|
|
|
private enum _stridedCode = q{
|
|
assert(factor > 0, "factor must be positive"
|
|
~ tailErrorMessage!());
|
|
immutable rem = slice._lengths[dimension] % factor;
|
|
slice._lengths[dimension] /= factor;
|
|
if (slice._lengths[dimension]) //do not remove `if (...)`
|
|
slice._strides[dimension] *= factor;
|
|
if (rem)
|
|
slice._lengths[dimension]++;
|
|
};
|
|
|
|
/++
|
|
Multiplies the stride of the selected dimension by a factor.
|
|
|
|
Params:
|
|
slice = input slice
|
|
Dimensions = indexes of dimensions to be strided
|
|
dimensions = indexes of dimensions to be strided
|
|
factors = list of step extension factors
|
|
factor = step extension factors
|
|
Returns:
|
|
n-dimensional slice of the same type
|
|
+/
|
|
template strided(Dimensions...)
|
|
if (Dimensions.length)
|
|
{
|
|
static if (!allSatisfy!(isSize_t, Dimensions))
|
|
alias strided = .strided!(staticMap!(toSize_t, Dimensions));
|
|
else
|
|
@fmb auto strided(size_t N, Range)(Slice!(N, Range) slice, Repeat!(Dimensions.length, size_t) factors)
|
|
body
|
|
{
|
|
foreach (i, dimension; Dimensions)
|
|
{
|
|
mixin DimensionCTError;
|
|
immutable factor = factors[i];
|
|
mixin (_stridedCode);
|
|
}
|
|
return slice;
|
|
}
|
|
}
|
|
|
|
///ditto
|
|
Slice!(N, Range) strided(size_t N, Range)(Slice!(N, Range) slice, size_t dimension, size_t factor)
|
|
in
|
|
{
|
|
mixin (DimensionRTError);
|
|
}
|
|
body
|
|
{
|
|
mixin (_stridedCode);
|
|
return slice;
|
|
}
|
|
|
|
///
|
|
pure nothrow @system unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
auto slice
|
|
= [0,1,2,3, 4,5,6,7, 8,9,10,11].sliced(3, 4);
|
|
|
|
assert(slice
|
|
== [[0,1,2,3], [4,5,6,7], [8,9,10,11]]);
|
|
|
|
// Template
|
|
assert(slice.strided!0(2)
|
|
== [[0,1,2,3], [8,9,10,11]]);
|
|
|
|
assert(slice.strided!1(3)
|
|
== [[0, 3], [4, 7], [8, 11]]);
|
|
|
|
assert(slice.strided!(0, 1)(2, 3)
|
|
== [[0, 3], [8, 11]]);
|
|
|
|
// Function
|
|
assert(slice.strided(0, 2)
|
|
== [[0,1,2,3], [8,9,10,11]]);
|
|
|
|
assert(slice.strided(1, 3)
|
|
== [[0, 3], [4, 7], [8, 11]]);
|
|
|
|
assert(slice.strided(0, 2).strided(1, 3)
|
|
== [[0, 3], [8, 11]]);
|
|
}
|
|
|
|
///
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
static assert(iotaSlice(13, 40).strided!(0, 1)(2, 5).shape == [7, 8]);
|
|
static assert(iotaSlice(93).strided!(0, 0)(7, 3).shape == [5]);
|
|
}
|
|
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection;
|
|
import std.algorithm.comparison : equal;
|
|
import std.range : iota, stride, chain;
|
|
auto i0 = iota(0, 4); auto s0 = i0.stride(3);
|
|
auto i1 = iota(4, 8); auto s1 = i1.stride(3);
|
|
auto i2 = iota(8, 12); auto s2 = i2.stride(3);
|
|
auto slice = 12.iota.sliced(3, 4);
|
|
assert(slice .byElement.equal(chain(i0, i1, i2)));
|
|
// Template
|
|
assert(slice.strided!0(2) .byElement.equal(chain(i0, i2)));
|
|
assert(slice.strided!1(3) .byElement.equal(chain(s0, s1, s2)));
|
|
assert(slice.strided!(0, 1)(2, 3).byElement.equal(chain(s0, s2)));
|
|
// Function
|
|
assert(slice.strided(0, 2).byElement.equal(chain(i0, i2)));
|
|
assert(slice.strided(1, 3).byElement.equal(chain(s0, s1, s2)));
|
|
assert(slice.strided(0, 2).strided(1, 3).byElement.equal(chain(s0, s2)));
|
|
}
|
|
|
|
/++
|
|
Convenience function which calls `slice.popFront!dimension()` for each dimension and returns the slice.
|
|
|
|
`allDropBackOne` provides the same functionality but calls `slice.popBack!dimension()` instead.
|
|
|
|
Params:
|
|
slice = input slice
|
|
Returns:
|
|
n-dimensional slice of the same type
|
|
+/
|
|
Slice!(N, Range) allDropOne(size_t N, Range)(Slice!(N, Range) slice)
|
|
{
|
|
foreach (dimension; Iota!(0, N))
|
|
slice.popFront!dimension;
|
|
return slice;
|
|
}
|
|
|
|
///ditto
|
|
Slice!(N, Range) allDropBackOne(size_t N, Range)(Slice!(N, Range) slice)
|
|
{
|
|
foreach (dimension; Iota!(0, N))
|
|
slice.popBack!dimension;
|
|
return slice;
|
|
}
|
|
|
|
///
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
auto a = iotaSlice(4, 5);
|
|
|
|
assert(a.allDropOne[0, 0] == 6);
|
|
assert(a.allDropOne.shape == cast(size_t[2])[3, 4]);
|
|
assert(a.allDropBackOne[$ - 1, $ - 1] == 13);
|
|
assert(a.allDropBackOne.shape == cast(size_t[2])[3, 4]);
|
|
}
|
|
|
|
/++
|
|
These functions are similar to `allDrop` and `allDropBack` but they call
|
|
`slice.popFrontExactly!dimension(n)` and `slice.popBackExactly!dimension(n)` instead.
|
|
|
|
Note:
|
|
Unlike `allDrop`, `allDropExactly(n)` assume that the slice holds
|
|
a multi-dimensional cube with a size of at least n.
|
|
This makes `allDropExactly` faster than `allDrop`.
|
|
Only use `allDropExactly` when it is guaranteed that the slice holds
|
|
a multi-dimensional cube with a size of at least n.
|
|
|
|
Params:
|
|
slice = input slice
|
|
n = number of elements to drop
|
|
Returns:
|
|
n-dimensional slice of the same type
|
|
+/
|
|
Slice!(N, Range) allDropExactly(size_t N, Range)(Slice!(N, Range) slice, size_t n)
|
|
{
|
|
foreach (dimension; Iota!(0, N))
|
|
slice.popFrontExactly!dimension(n);
|
|
return slice;
|
|
}
|
|
|
|
///ditto
|
|
Slice!(N, Range) allDropBackExactly(size_t N, Range)(Slice!(N, Range) slice, size_t n)
|
|
{
|
|
foreach (dimension; Iota!(0, N))
|
|
slice.popBackExactly!dimension(n);
|
|
return slice;
|
|
}
|
|
|
|
///
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
auto a = iotaSlice(4, 5);
|
|
|
|
assert(a.allDropExactly(2)[0, 0] == 12);
|
|
assert(a.allDropExactly(2).shape == cast(size_t[2])[2, 3]);
|
|
assert(a.allDropBackExactly(2)[$ - 1, $ - 1] == 7);
|
|
assert(a.allDropBackExactly(2).shape == cast(size_t[2])[2, 3]);
|
|
}
|
|
|
|
/++
|
|
Convenience function which calls `slice.popFrontN!dimension(n)` for each dimension and returns the slice.
|
|
|
|
`allDropBack` provides the same functionality but calls `slice.popBackN!dimension(n)` instead.
|
|
|
|
Note:
|
|
`allDrop` and `allDropBack` remove up to n elements and stop when the slice is empty.
|
|
|
|
Params:
|
|
slice = input slice
|
|
n = number of elements to drop
|
|
Returns:
|
|
n-dimensional slice of the same type
|
|
+/
|
|
Slice!(N, Range) allDrop(size_t N, Range)(Slice!(N, Range) slice, size_t n)
|
|
{
|
|
foreach (dimension; Iota!(0, N))
|
|
slice.popFrontN!dimension(n);
|
|
return slice;
|
|
}
|
|
|
|
///ditto
|
|
Slice!(N, Range) allDropBack(size_t N, Range)(Slice!(N, Range) slice, size_t n)
|
|
{
|
|
foreach (dimension; Iota!(0, N))
|
|
slice.popBackN!dimension(n);
|
|
return slice;
|
|
}
|
|
|
|
///
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
auto a = iotaSlice(4, 5);
|
|
|
|
assert(a.allDrop(2)[0, 0] == 12);
|
|
assert(a.allDrop(2).shape == cast(size_t[2])[2, 3]);
|
|
assert(a.allDropBack(2)[$ - 1, $ - 1] == 7);
|
|
assert(a.allDropBack(2).shape == cast(size_t[2])[2, 3]);
|
|
|
|
assert(a.allDrop (5).shape == cast(size_t[2])[0, 0]);
|
|
assert(a.allDropBack(5).shape == cast(size_t[2])[0, 0]);
|
|
}
|
|
|
|
/++
|
|
Convenience function which calls `slice.popFront!dimension()` for selected dimensions and returns the slice.
|
|
|
|
`dropBackOne` provides the same functionality but calls `slice.popBack!dimension()` instead.
|
|
|
|
Params:
|
|
slice = input slice
|
|
Returns:
|
|
n-dimensional slice of the same type
|
|
+/
|
|
template dropOne(Dimensions...)
|
|
if (Dimensions.length)
|
|
{
|
|
static if (!allSatisfy!(isSize_t, Dimensions))
|
|
alias dropOne = .dropOne!(staticMap!(toSize_t, Dimensions));
|
|
else
|
|
@fmb Slice!(N, Range) dropOne(size_t N, Range)(Slice!(N, Range) slice)
|
|
{
|
|
foreach (i, dimension; Dimensions)
|
|
{
|
|
mixin DimensionCTError;
|
|
slice.popFront!dimension;
|
|
}
|
|
return slice;
|
|
}
|
|
}
|
|
|
|
///ditto
|
|
Slice!(N, Range) dropOne(size_t N, Range, size_t M)(Slice!(N, Range) slice, size_t[M] dimensions...)
|
|
in
|
|
{
|
|
foreach (dimension; dimensions)
|
|
mixin (DimensionRTError);
|
|
}
|
|
body
|
|
{
|
|
foreach (i; Iota!(0, M))
|
|
{
|
|
auto dimension = dimensions[i];
|
|
slice.popFront(dimension);
|
|
}
|
|
return slice;
|
|
}
|
|
|
|
///ditto
|
|
template dropBackOne(Dimensions...)
|
|
if (Dimensions.length)
|
|
{
|
|
static if (!allSatisfy!(isSize_t, Dimensions))
|
|
alias dropBackOne = .dropBackOne!(staticMap!(toSize_t, Dimensions));
|
|
else
|
|
@fmb Slice!(N, Range) dropBackOne(size_t N, Range)(Slice!(N, Range) slice)
|
|
{
|
|
foreach (i, dimension; Dimensions)
|
|
{
|
|
mixin DimensionCTError;
|
|
slice.popBack!dimension;
|
|
}
|
|
return slice;
|
|
}
|
|
}
|
|
|
|
///ditto
|
|
Slice!(N, Range) dropBackOne(size_t N, Range, size_t M)(Slice!(N, Range) slice, size_t[M] dimensions...)
|
|
in
|
|
{
|
|
foreach (dimension; dimensions)
|
|
mixin (DimensionRTError);
|
|
}
|
|
body
|
|
{
|
|
foreach (i; Iota!(0, M))
|
|
{
|
|
auto dimension = dimensions[i];
|
|
slice.popBack(dimension);
|
|
}
|
|
return slice;
|
|
}
|
|
|
|
|
|
///
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
auto a = iotaSlice(4, 5);
|
|
|
|
assert(a.dropOne!(1, 0)[0, 0] == 6);
|
|
assert(a.dropOne (1, 0)[0, 0] == 6);
|
|
assert(a.dropOne!(1, 0).shape == cast(size_t[2])[3, 4]);
|
|
assert(a.dropOne (1, 0).shape == cast(size_t[2])[3, 4]);
|
|
assert(a.dropBackOne!(1, 0)[$ - 1, $ - 1] == 13);
|
|
assert(a.dropBackOne (1, 0)[$ - 1, $ - 1] == 13);
|
|
assert(a.dropBackOne!(1, 0).shape == cast(size_t[2])[3, 4]);
|
|
assert(a.dropBackOne (1, 0).shape == cast(size_t[2])[3, 4]);
|
|
|
|
assert(a.dropOne!(0, 0)[0, 0] == 10);
|
|
assert(a.dropOne (0, 0)[0, 0] == 10);
|
|
assert(a.dropOne!(0, 0).shape == cast(size_t[2])[2, 5]);
|
|
assert(a.dropOne (0, 0).shape == cast(size_t[2])[2, 5]);
|
|
assert(a.dropBackOne!(1, 1)[$ - 1, $ - 1] == 17);
|
|
assert(a.dropBackOne (1, 1)[$ - 1, $ - 1] == 17);
|
|
assert(a.dropBackOne!(1, 1).shape == cast(size_t[2])[4, 3]);
|
|
assert(a.dropBackOne (1, 1).shape == cast(size_t[2])[4, 3]);
|
|
}
|
|
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
auto a = iotaSlice(4, 5);
|
|
|
|
assert(a.dropOne(0).dropOne(0)[0, 0] == 10);
|
|
assert(a.dropOne(0).dropOne(0).shape == cast(size_t[2])[2, 5]);
|
|
assert(a.dropBackOne(1).dropBackOne(1)[$ - 1, $ - 1] == 17);
|
|
assert(a.dropBackOne(1).dropBackOne(1).shape == cast(size_t[2])[4, 3]);
|
|
}
|
|
|
|
|
|
/++
|
|
These functions are similar to `drop` and `dropBack` but they call
|
|
`slice.popFrontExactly!dimension(n)` and `slice.popBackExactly!dimension(n)` instead.
|
|
|
|
Note:
|
|
Unlike `drop`, `dropExactly` assumes that the slice holds enough elements in
|
|
the selected dimension.
|
|
This makes `dropExactly` faster than `drop`.
|
|
|
|
Params:
|
|
slice = input slice
|
|
ns = list of numbers of elements to drop
|
|
n = number of elements to drop
|
|
Returns:
|
|
n-dimensional slice of the same type
|
|
+/
|
|
template dropExactly(Dimensions...)
|
|
if (Dimensions.length)
|
|
{
|
|
static if (!allSatisfy!(isSize_t, Dimensions))
|
|
alias dropExactly = .dropExactly!(staticMap!(toSize_t, Dimensions));
|
|
else
|
|
@fmb Slice!(N, Range) dropExactly(size_t N, Range)(Slice!(N, Range) slice, Repeat!(Dimensions.length, size_t) ns)
|
|
body
|
|
{
|
|
foreach (i, dimension; Dimensions)
|
|
{
|
|
mixin DimensionCTError;
|
|
slice.popFrontExactly!dimension(ns[i]);
|
|
}
|
|
return slice;
|
|
}
|
|
}
|
|
|
|
///ditto
|
|
Slice!(N, Range) dropExactly(size_t N, Range)(Slice!(N, Range) slice, size_t dimension, size_t n)
|
|
in
|
|
{
|
|
mixin (DimensionRTError);
|
|
}
|
|
body
|
|
{
|
|
slice.popFrontExactly(dimension, n);
|
|
return slice;
|
|
}
|
|
|
|
///ditto
|
|
template dropBackExactly(Dimensions...)
|
|
if (Dimensions.length)
|
|
{
|
|
static if (!allSatisfy!(isSize_t, Dimensions))
|
|
alias dropBackExactly = .dropBackExactly!(staticMap!(toSize_t, Dimensions));
|
|
else
|
|
@fmb Slice!(N, Range) dropBackExactly(size_t N, Range)(Slice!(N, Range) slice,
|
|
Repeat!(Dimensions.length, size_t) ns)
|
|
body
|
|
{
|
|
foreach (i, dimension; Dimensions)
|
|
{
|
|
mixin DimensionCTError;
|
|
slice.popBackExactly!dimension(ns[i]);
|
|
}
|
|
return slice;
|
|
}
|
|
}
|
|
|
|
///ditto
|
|
Slice!(N, Range) dropBackExactly(size_t N, Range)(Slice!(N, Range) slice, size_t dimension, size_t n)
|
|
in
|
|
{
|
|
mixin (DimensionRTError);
|
|
}
|
|
body
|
|
{
|
|
slice.popBackExactly(dimension, n);
|
|
return slice;
|
|
}
|
|
|
|
///
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
auto a = iotaSlice(4, 5);
|
|
|
|
assert(a.dropExactly !(1, 0)(2, 3)[0, 0] == 17);
|
|
assert(a.dropExactly !(1, 0)(2, 3).shape == cast(size_t[2])[1, 3]);
|
|
assert(a.dropBackExactly!(0, 1)(2, 3)[$ - 1, $ - 1] == 6);
|
|
assert(a.dropBackExactly!(0, 1)(2, 3).shape == cast(size_t[2])[2, 2]);
|
|
|
|
assert(a.dropExactly(1, 2).dropExactly(0, 3)[0, 0] == 17);
|
|
assert(a.dropExactly(1, 2).dropExactly(0, 3).shape == cast(size_t[2])[1, 3]);
|
|
assert(a.dropBackExactly(0, 2).dropBackExactly(1, 3)[$ - 1, $ - 1] == 6);
|
|
assert(a.dropBackExactly(0, 2).dropBackExactly(1, 3).shape == cast(size_t[2])[2, 2]);
|
|
}
|
|
|
|
/++
|
|
Convenience function which calls `slice.popFrontN!dimension(n)` for the selected
|
|
dimension and returns the slice.
|
|
|
|
`dropBack` provides the same functionality but calls `slice.popBackN!dimension(n)` instead.
|
|
|
|
Note:
|
|
`drop` and `dropBack` remove up to n elements and stop when the slice is empty.
|
|
|
|
Params:
|
|
slice = input slice
|
|
ns = list of numbers of elements to drop
|
|
n = number of elements to drop
|
|
Returns:
|
|
n-dimensional slice of the same type
|
|
+/
|
|
template drop(Dimensions...)
|
|
if (Dimensions.length)
|
|
{
|
|
static if (!allSatisfy!(isSize_t, Dimensions))
|
|
alias drop = .drop!(staticMap!(toSize_t, Dimensions));
|
|
else
|
|
@fmb Slice!(N, Range) drop(size_t N, Range)(Slice!(N, Range) slice, Repeat!(Dimensions.length, size_t) ns)
|
|
body
|
|
{
|
|
foreach (i, dimension; Dimensions)
|
|
{
|
|
mixin DimensionCTError;
|
|
slice.popFrontN!dimension(ns[i]);
|
|
}
|
|
return slice;
|
|
}
|
|
}
|
|
|
|
///ditto
|
|
Slice!(N, Range) drop(size_t N, Range)(Slice!(N, Range) slice, size_t dimension, size_t n)
|
|
in
|
|
{
|
|
mixin (DimensionRTError);
|
|
}
|
|
body
|
|
{
|
|
slice.popFrontN(dimension, n);
|
|
return slice;
|
|
}
|
|
|
|
///ditto
|
|
template dropBack(Dimensions...)
|
|
if (Dimensions.length)
|
|
{
|
|
static if (!allSatisfy!(isSize_t, Dimensions))
|
|
alias dropBack = .dropBack!(staticMap!(toSize_t, Dimensions));
|
|
else
|
|
@fmb Slice!(N, Range) dropBack(size_t N, Range)(Slice!(N, Range) slice, Repeat!(Dimensions.length, size_t) ns)
|
|
body
|
|
{
|
|
foreach (i, dimension; Dimensions)
|
|
{
|
|
mixin DimensionCTError;
|
|
slice.popBackN!dimension(ns[i]);
|
|
}
|
|
return slice;
|
|
}
|
|
}
|
|
|
|
///ditto
|
|
Slice!(N, Range) dropBack(size_t N, Range)(Slice!(N, Range) slice, size_t dimension, size_t n)
|
|
in
|
|
{
|
|
mixin (DimensionRTError);
|
|
}
|
|
body
|
|
{
|
|
slice.popBackN(dimension, n);
|
|
return slice;
|
|
}
|
|
|
|
|
|
///
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.slice;
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
auto a = iotaSlice(4, 5);
|
|
|
|
assert(a.drop !(1, 0)(2, 3)[0, 0] == 17);
|
|
assert(a.drop !(1, 0)(2, 3).shape == cast(size_t[2])[1, 3]);
|
|
assert(a.dropBack!(0, 1)(2, 3)[$ - 1, $ - 1] == 6);
|
|
assert(a.dropBack!(0, 1)(2, 3).shape == cast(size_t[2])[2, 2]);
|
|
assert(a.dropBack!(0, 1)(5, 5).shape == cast(size_t[2])[0, 0]);
|
|
|
|
|
|
assert(a.drop(1, 2).drop(0, 3)[0, 0] == 17);
|
|
assert(a.drop(1, 2).drop(0, 3).shape == cast(size_t[2])[1, 3]);
|
|
assert(a.dropBack(0, 2).dropBack(1, 3)[$ - 1, $ - 1] == 6);
|
|
assert(a.dropBack(0, 2).dropBack(1, 3).shape == cast(size_t[2])[2, 2]);
|
|
assert(a.dropBack(0, 5).dropBack(1, 5).shape == cast(size_t[2])[0, 0]);
|
|
}
|
|
|
|
/++
|
|
Returns maximal multidimensional cube.
|
|
|
|
Params:
|
|
slice = input slice
|
|
Returns:
|
|
n-dimensional slice of the same type
|
|
+/
|
|
Slice!(N, Range) dropToHypercube(size_t N, Range)(Slice!(N, Range) slice)
|
|
body
|
|
{
|
|
size_t length = slice._lengths[0];
|
|
foreach (i; Iota!(1, N))
|
|
if (length > slice._lengths[i])
|
|
length = slice._lengths[i];
|
|
foreach (i; Iota!(0, N))
|
|
slice._lengths[i] = length;
|
|
return slice;
|
|
}
|
|
|
|
///
|
|
@safe @nogc pure nothrow unittest
|
|
{
|
|
import std.experimental.ndslice.selection : iotaSlice;
|
|
assert(iotaSlice(5, 3, 6, 7)
|
|
.dropToHypercube
|
|
.shape == cast(size_t[4])[3, 3, 3, 3]);
|
|
}
|