phobos/std/experimental/ndslice/selection.d
Sebastian Wilzbach 2dfbc51f17 Standardize whitespace after imports
Unified with:

sed -E "s/import\s*([^ ]+)\s*:\s*(.*(,|;))/import \1 : \2/" -i **/*.d
2016-05-29 22:09:56 +02:00

1739 lines
46 KiB
D

/**
$(SCRIPT inhibitQuickIndex = 1;)
This is a submodule of $(MREF std, experimental, ndslice).
Selectors create new views and iteration patterns over the same data, without copying.
$(H2 Subspace selectors)
Subspace selectors serve to generalize and combine other selectors easily.
For a slice of `Slice!(N, Range)` type `slice.pack!K` creates a slice of
slices of `Slice!(N-K, Slice!(K+1, Range))` type by packing
the last `K` dimensions of the top dimension pack,
and the type of element of `slice.byElement` is `Slice!(K, Range)`.
Another way to use $(LREF pack) is transposition of dimension packs using
$(LREF evertPack). Examples of use of subspace selectors are available for selectors,
$(SUBREF slice, Slice.shape), and $(SUBREF slice, Slice.elementsCount).
$(BOOKTABLE ,
$(TR $(TH Function Name) $(TH Description))
$(T2 pack , returns slice of slices)
$(T2 unpack , merges all dimension packs)
$(T2 evertPack, reverses dimension packs)
)
$(BOOKTABLE $(H2 Selectors),
$(TR $(TH Function Name) $(TH Description))
$(T2 byElement, flat, random access range of all elements with `index` property)
$(T2 byElementInStandardSimplex, an input range of all elements in standard simplex of hypercube with `index` property.
If the slice has two dimensions, it is a range of all elements of upper left triangular matrix.)
$(T2 indexSlice, lazy slice with initial multidimensional index)
$(T2 iotaSlice, lazy slice with initial flattened (continuous) index)
$(T2 reshape, new slice with changed dimensions for the same data)
$(T2 diagonal, 1-dimensional slice composed of diagonal elements)
$(T2 blocks, n-dimensional slice composed of n-dimensional non-overlapping blocks.
If the slice has two dimensions, it is a block matrix.)
$(T2 windows, n-dimensional slice of n-dimensional overlapping windows.
If the slice has two dimensions, it is a sliding window.)
)
License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
Authors: Ilya Yaroshenko
Source: $(PHOBOSSRC std/_experimental/_ndslice/_selection.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))
*/
module std.experimental.ndslice.selection;
import std.traits;
import std.meta; //: allSatisfy;
import std.experimental.ndslice.internal;
import std.experimental.ndslice.slice; //: Slice;
/++
Creates a packed slice, i.e. slice of slices.
The function does not carry out any calculations, it simply returns the same
binary data presented differently.
Params:
K = sizes of dimension packs
Returns:
`pack!K` returns `Slice!(N-K, Slice!(K+1, Range))`;
`slice.pack!(K1, K2, ..., Kn)` is the same as `slice.pack!K1.pack!K2. ... pack!Kn`.
+/
template pack(K...)
{
auto pack(size_t N, Range)(auto ref Slice!(N, Range) slice)
{
template Template(size_t NInner, Range, R...)
{
static if (R.length > 0)
{
static if (NInner > R[0])
alias Template = Template!(NInner - R[0], Slice!(R[0] + 1, Range), R[1 .. $]);
else
static assert(0,
"Sum of all lengths of packs " ~ K.stringof
~ " should be less than N = "~ N.stringof
~ tailErrorMessage!());
}
else
{
alias Template = Slice!(NInner, Range);
}
}
with (slice) return Template!(N, Range, K)(_lengths, _strides, _ptr);
}
}
///
@safe @nogc pure nothrow unittest
{
import std.experimental.ndslice.slice;
import std.range.primitives : ElementType;
import std.range : iota;
auto r = (3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11).iota;
auto a = r.sliced(3, 4, 5, 6, 7, 8, 9, 10, 11);
auto b = a.pack!(2, 3); // same as `a.pack!2.pack!3`
auto c = b[1, 2, 3, 4];
auto d = c[5, 6, 7];
auto e = d[8, 9];
auto g = a[1, 2, 3, 4, 5, 6, 7, 8, 9];
assert(e == g);
assert(a == b);
assert(c == a[1, 2, 3, 4]);
alias R = typeof(r);
static assert(is(typeof(b) == typeof(a.pack!2.pack!3)));
static assert(is(typeof(b) == Slice!(4, Slice!(4, Slice!(3, R)))));
static assert(is(typeof(c) == Slice!(3, Slice!(3, R))));
static assert(is(typeof(d) == Slice!(2, R)));
static assert(is(typeof(e) == ElementType!R));
}
@safe @nogc pure nothrow unittest
{
auto a = iotaSlice(3, 4, 5, 6, 7, 8, 9, 10, 11);
auto b = a.pack!(2, 3);
static assert(b.shape.length == 4);
static assert(b.structure.lengths.length == 4);
static assert(b.structure.strides.length == 4);
static assert(b
.byElement.front
.shape.length == 3);
static assert(b
.byElement.front
.byElement.front
.shape.length == 2);
// test save
b.byElement.save.popFront;
static assert(b
.byElement.front
.shape.length == 3);
}
/++
Unpacks a packed slice.
The function does not carry out any calculations, it simply returns the same
binary data presented differently.
Params:
slice = packed slice
Returns:
unpacked slice
See_also: $(LREF pack), $(LREF evertPack)
+/
Slice!(N, Range).PureThis unpack(size_t N, Range)(auto ref Slice!(N, Range) slice)
{
with (slice) return PureThis(_lengths, _strides, _ptr);
}
///
pure nothrow unittest
{
auto a = iotaSlice(3, 4, 5, 6, 7, 8, 9, 10, 11);
auto b = a.pack!(2, 3).unpack();
static assert(is(typeof(a) == typeof(b)));
assert(a == b);
}
/++
Reverses the order of dimension packs.
This function is used in a functional pipeline with other selectors.
Params:
slice = packed slice
Returns:
packed slice
See_also: $(LREF pack), $(LREF unpack)
+/
SliceFromSeq!(Slice!(N, Range).PureRange, NSeqEvert!(Slice!(N, Range).NSeq))
evertPack(size_t N, Range)(auto ref Slice!(N, Range) slice)
{
mixin _DefineRet;
static assert(Ret.NSeq.length > 0);
with (slice)
{
alias C = Snowball!(Parts!NSeq);
alias D = Reverse!(Snowball!(Reverse!(Parts!NSeq)));
foreach (i, _; NSeq)
{
foreach (j; Iota!(0, C[i + 1] - C[i]))
{
ret._lengths[j + D[i + 1]] = _lengths[j + C[i]];
ret._strides[j + D[i + 1]] = _strides[j + C[i]];
}
}
ret._ptr = _ptr;
return ret;
}
}
///
@safe @nogc pure nothrow unittest
{
import std.experimental.ndslice.iteration : transposed;
auto slice = iotaSlice(3, 4, 5, 6, 7, 8, 9, 10, 11);
assert(slice
.pack!2
.evertPack
.unpack
== slice.transposed!(
slice.shape.length-2,
slice.shape.length-1));
}
///
pure nothrow unittest
{
import std.experimental.ndslice.slice;
import std.experimental.ndslice.iteration : transposed;
import std.range.primitives : ElementType;
import std.range : iota;
import std.algorithm.comparison : equal;
auto r = (3 * 4 * 5 * 6 * 7 * 8 * 9 * 10 * 11).iota;
auto a = r.sliced(3, 4, 5, 6, 7, 8, 9, 10, 11);
auto b = a
.pack!(2, 3)
.evertPack;
auto c = b[8, 9];
auto d = c[5, 6, 7];
auto e = d[1, 2, 3, 4];
auto g = a[1, 2, 3, 4, 5, 6, 7, 8, 9];
assert(e == g);
assert(a == b.evertPack);
assert(c == a.transposed!(7, 8, 4, 5, 6)[8, 9]);
alias R = typeof(r);
static assert(is(typeof(b) == Slice!(2, Slice!(4, Slice!(5, R)))));
static assert(is(typeof(c) == Slice!(3, Slice!(5, R))));
static assert(is(typeof(d) == Slice!(4, R)));
static assert(is(typeof(e) == ElementType!R));
}
@safe @nogc pure nothrow unittest
{
import std.experimental.ndslice.slice;
static assert(is(typeof(slice!int(20)
.evertPack)
== Slice!(1LU, int*)));
static assert(is(typeof(slice!int(20)
.sliced(3)
.evertPack)
== Slice!(2LU, int*)));
static assert(is(typeof(slice!int(20)
.sliced(1,2,3)
.sliced(3)
.evertPack)
== Slice!(3LU, Slice!(2LU, int*))));
static assert(is(typeof(slice!int(20)
.sliced(1,2,3)
.evertPack)
== Slice!(4LU, int*)));
}
/++
Returns a 1-dimensional slice over the main diagonal of an n-dimensional slice.
`diagonal` can be generalized with other selectors such as
$(LREF blocks) (diagonal blocks) and $(LREF windows) (multi-diagonal slice).
Params:
N = dimension count
slice = input slice
Returns:
1-dimensional slice composed of diagonal elements
+/
Slice!(1, Range) diagonal(size_t N, Range)(auto ref Slice!(N, Range) slice)
{
auto NewN = slice.PureN - N + 1;
mixin _DefineRet;
ret._lengths[0] = slice._lengths[0];
ret._strides[0] = slice._strides[0];
foreach (i; Iota!(1, N))
{
if (ret._lengths[0] > slice._lengths[i])
ret._lengths[0] = slice._lengths[i];
ret._strides[0] += slice._strides[i];
}
foreach (i; Iota!(1, ret.PureN))
{
ret._lengths[i] = slice._lengths[i + N - 1];
ret._strides[i] = slice._strides[i + N - 1];
}
ret._ptr = slice._ptr;
return ret;
}
/// Matrix, main diagonal
@safe @nogc pure nothrow unittest
{
// -------
// | 0 1 2 |
// | 3 4 5 |
// -------
//->
// | 0 4 |
static immutable d = [0, 4];
assert(iotaSlice(2, 3).diagonal == d);
}
/// Non-square matrix
@safe @nogc pure nothrow unittest
{
import std.algorithm.comparison : equal;
import std.range : only;
// -------
// | 0 1 |
// | 2 3 |
// | 4 5 |
// -------
//->
// | 0 3 |
assert(iotaSlice(3, 2)
.diagonal
.equal(only(0, 3)));
}
/// Loop through diagonal
pure nothrow unittest
{
import std.experimental.ndslice.slice;
auto slice = slice!int(3, 3);
int i;
foreach (ref e; slice.diagonal)
e = ++i;
assert(slice == [
[1, 0, 0],
[0, 2, 0],
[0, 0, 3]]);
}
/// Matrix, subdiagonal
@safe @nogc pure nothrow unittest
{
import std.experimental.ndslice.iteration : dropOne;
// -------
// | 0 1 2 |
// | 3 4 5 |
// -------
//->
// | 1 5 |
static immutable d = [1, 5];
assert(iotaSlice(2, 3).dropOne!1.diagonal == d);
}
/// Matrix, antidiagonal
@safe @nogc pure nothrow unittest
{
import std.experimental.ndslice.iteration : dropToHypercube, reversed;
// -------
// | 0 1 2 |
// | 3 4 5 |
// -------
//->
// | 1 3 |
static immutable d = [1, 3];
assert(iotaSlice(2, 3).dropToHypercube.reversed!1.diagonal == d);
}
/// 3D, main diagonal
@safe @nogc pure nothrow unittest
{
// -----------
// | 0 1 2 |
// | 3 4 5 |
// - - - - - -
// | 6 7 8 |
// | 9 10 11 |
// -----------
//->
// | 0 10 |
static immutable d = [0, 10];
assert(iotaSlice(2, 2, 3).diagonal == d);
}
/// 3D, subdiagonal
@safe @nogc pure nothrow unittest
{
import std.experimental.ndslice.iteration : dropOne;
// -----------
// | 0 1 2 |
// | 3 4 5 |
// - - - - - -
// | 6 7 8 |
// | 9 10 11 |
// -----------
//->
// | 1 11 |
static immutable d = [1, 11];
assert(iotaSlice(2, 2, 3).dropOne!2.diagonal == d);
}
/// 3D, diagonal plain
@nogc @safe pure nothrow unittest
{
import std.experimental.ndslice.iteration : dropOne;
// -----------
// | 0 1 2 |
// | 3 4 5 |
// | 6 7 8 |
// - - - - - -
// | 9 10 11 |
// | 12 13 14 |
// | 15 16 17 |
// - - - - - -
// | 18 20 21 |
// | 22 23 24 |
// | 24 25 26 |
// -----------
//->
// -----------
// | 0 4 8 |
// | 9 13 17 |
// | 18 23 26 |
// -----------
static immutable d =
[[ 0, 4, 8],
[ 9, 13, 17],
[18, 22, 26]];
auto slice = iotaSlice(3, 3, 3)
.pack!2
.evertPack
.diagonal
.evertPack;
assert(slice == d);
}
/++
Returns an n-dimensional slice of n-dimensional non-overlapping blocks.
`blocks` can be generalized with other selectors.
For example, `blocks` in combination with $(LREF diagonal) can be used to get a slice of diagonal blocks.
Params:
N = dimension count
slice = slice to be split into blocks
lengths = dimensions of block, residual blocks are ignored
Returns:
packed `N`-dimensional slice composed of `N`-dimensional slices
+/
Slice!(N, Slice!(N+1, Range)) blocks(size_t N, Range, Lengths...)(auto ref Slice!(N, Range) slice, Lengths lengths)
if (allSatisfy!(isIndex, Lengths) && Lengths.length == N)
in
{
foreach (i, length; lengths)
assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive"
~ tailErrorMessage!());
}
body
{
mixin _DefineRet;
foreach (dimension; Iota!(0, N))
{
ret._lengths[dimension] = slice._lengths[dimension] / lengths[dimension];
ret._strides[dimension] = slice._strides[dimension];
if (ret._lengths[dimension]) //do not remove `if (...)`
ret._strides[dimension] *= lengths[dimension];
ret._lengths[dimension + N] = lengths[dimension];
ret._strides[dimension + N] = slice._strides[dimension];
}
foreach (dimension; Iota!(N, slice.PureN))
{
ret._lengths[dimension + N] = slice._lengths[dimension];
ret._strides[dimension + N] = slice._strides[dimension];
}
ret._ptr = slice._ptr;
return ret;
}
///
pure nothrow unittest
{
import std.experimental.ndslice.slice;
auto slice = slice!int(5, 8);
auto blocks = slice.blocks(2, 3);
int i;
foreach (block; blocks.byElement)
block[] = ++i;
assert(blocks ==
[[[[1, 1, 1], [1, 1, 1]],
[[2, 2, 2], [2, 2, 2]]],
[[[3, 3, 3], [3, 3, 3]],
[[4, 4, 4], [4, 4, 4]]]]);
assert( slice ==
[[1, 1, 1, 2, 2, 2, 0, 0],
[1, 1, 1, 2, 2, 2, 0, 0],
[3, 3, 3, 4, 4, 4, 0, 0],
[3, 3, 3, 4, 4, 4, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0]]);
}
/// Diagonal blocks
pure nothrow unittest
{
import std.experimental.ndslice.slice;
auto slice = slice!int(5, 8);
auto blocks = slice.blocks(2, 3);
auto diagonalBlocks = blocks.diagonal.unpack;
diagonalBlocks[0][] = 1;
diagonalBlocks[1][] = 2;
assert(diagonalBlocks ==
[[[1, 1, 1], [1, 1, 1]],
[[2, 2, 2], [2, 2, 2]]]);
assert(blocks ==
[[[[1, 1, 1], [1, 1, 1]],
[[0, 0, 0], [0, 0, 0]]],
[[[0, 0, 0], [0, 0, 0]],
[[2, 2, 2], [2, 2, 2]]]]);
assert(slice ==
[[1, 1, 1, 0, 0, 0, 0, 0],
[1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 2, 2, 2, 0, 0],
[0, 0, 0, 2, 2, 2, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0]]);
}
/// Matrix divided into vertical blocks
pure nothrow unittest
{
import std.experimental.ndslice.slice;
auto slice = slice!int(5, 13);
auto blocks = slice
.pack!1
.evertPack
.blocks(3)
.unpack
.pack!2;
int i;
foreach (block; blocks.byElement)
block[] = ++i;
assert(slice ==
[[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0],
[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0],
[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0],
[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0],
[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0]]);
}
/++
Returns an n-dimensional slice of n-dimensional overlapping windows.
`windows` can be generalized with other selectors.
For example, `windows` in combination with $(LREF diagonal) can be used to get a multi-diagonal slice.
Params:
N = dimension count
slice = slice to be iterated
lengths = dimensions of windows
Returns:
packed `N`-dimensional slice composed of `N`-dimensional slices
+/
Slice!(N, Slice!(N+1, Range)) windows(size_t N, Range, Lengths...)(auto ref Slice!(N, Range) slice, Lengths lengths)
if (allSatisfy!(isIndex, Lengths) && Lengths.length == N)
in
{
foreach (i, length; lengths)
assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive"
~ tailErrorMessage!());
}
body
{
mixin _DefineRet;
foreach (dimension; Iota!(0, N))
{
ret._lengths[dimension] = slice._lengths[dimension] >= lengths[dimension] ?
slice._lengths[dimension] - lengths[dimension] + 1: 0;
ret._strides[dimension] = slice._strides[dimension];
ret._lengths[dimension + N] = lengths[dimension];
ret._strides[dimension + N] = slice._strides[dimension];
}
foreach (dimension; Iota!(N, slice.PureN))
{
ret._lengths[dimension + N] = slice._lengths[dimension];
ret._strides[dimension + N] = slice._strides[dimension];
}
ret._ptr = slice._ptr;
return ret;
}
///
pure nothrow unittest
{
import std.experimental.ndslice.slice;
auto slice = slice!int(5, 8);
auto windows = slice.windows(2, 3);
foreach (window; windows.byElement)
window[] += 1;
assert(slice ==
[[1, 2, 3, 3, 3, 3, 2, 1],
[2, 4, 6, 6, 6, 6, 4, 2],
[2, 4, 6, 6, 6, 6, 4, 2],
[2, 4, 6, 6, 6, 6, 4, 2],
[1, 2, 3, 3, 3, 3, 2, 1]]);
}
///
pure nothrow unittest
{
import std.experimental.ndslice.slice;
auto slice = slice!int(5, 8);
auto windows = slice.windows(2, 3);
windows[1, 2][] = 1;
windows[1, 2][0, 1] += 1;
windows.unpack[1, 2, 0, 1] += 1;
assert(slice ==
[[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 3, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0]]);
}
/// Multi-diagonal matrix
pure nothrow unittest
{
import std.experimental.ndslice.slice;
auto slice = slice!int(8, 8);
auto windows = slice.windows(3, 3);
auto multidiagonal = windows
.diagonal
.unpack;
foreach (window; multidiagonal)
window[] += 1;
assert(slice ==
[[ 1, 1, 1, 0, 0, 0, 0, 0],
[ 1, 2, 2, 1, 0, 0, 0, 0],
[ 1, 2, 3, 2, 1, 0, 0, 0],
[0, 1, 2, 3, 2, 1, 0, 0],
[0, 0, 1, 2, 3, 2, 1, 0],
[0, 0, 0, 1, 2, 3, 2, 1],
[0, 0, 0, 0, 1, 2, 2, 1],
[0, 0, 0, 0, 0, 1, 1, 1]]);
}
/// Sliding window over matrix columns
pure nothrow unittest
{
import std.experimental.ndslice.slice;
auto slice = slice!int(5, 8);
auto windows = slice
.pack!1
.evertPack
.windows(3)
.unpack
.pack!2;
foreach (window; windows.byElement)
window[] += 1;
assert(slice ==
[[1, 2, 3, 3, 3, 3, 2, 1],
[1, 2, 3, 3, 3, 3, 2, 1],
[1, 2, 3, 3, 3, 3, 2, 1],
[1, 2, 3, 3, 3, 3, 2, 1],
[1, 2, 3, 3, 3, 3, 2, 1]]);
}
/++
Returns a new slice for the same data with different dimensions.
Params:
slice = slice to be reshaped
lengths = list of new dimensions. One of the lengths can be set to `-1`.
In this case, the corresponding dimension is inferable.
Returns:
reshaped slice
Throws:
$(LREF ReshapeException) if the slice cannot be reshaped with the input lengths.
+/
Slice!(Lengths.length, Range)
reshape
( size_t N, Range , Lengths... )
(auto ref Slice!(N, Range) slice, Lengths lengths)
if ( allSatisfy!(isIndex, Lengths) && Lengths.length)
{
mixin _DefineRet;
foreach (i; Iota!(0, ret.N))
ret._lengths[i] = lengths[i];
immutable size_t eco = slice.elementsCount;
size_t ecn = ret .elementsCount;
if (eco == 0)
throw new ReshapeException(
slice._lengths.dup,
slice._strides.dup,
ret. _lengths.dup,
"slice should be not empty");
foreach (i; Iota!(0, ret.N))
if (ret._lengths[i] == -1)
{
ecn = -ecn;
ret._lengths[i] = eco / ecn;
ecn *= ret._lengths[i];
break;
}
if (eco != ecn)
throw new ReshapeException(
slice._lengths.dup,
slice._strides.dup,
ret. _lengths.dup,
"total element count should be the same");
for (size_t oi, ni, oj, nj; oi < slice.N && ni < ret.N; oi = oj, ni = nj)
{
size_t op = slice._lengths[oj++];
size_t np = ret ._lengths[nj++];
for (;;)
{
if (op < np)
op *= slice._lengths[oj++];
if (op > np)
np *= ret ._lengths[nj++];
if (op == np)
break;
}
while (oj < slice.N && slice._lengths[oj] == 1) oj++;
while (nj < ret .N && ret ._lengths[nj] == 1) nj++;
for (size_t l = oi, r = oi + 1; r < oj; r++)
if (slice._lengths[r] != 1)
{
if (slice._strides[l] != slice._lengths[r] * slice._strides[r])
throw new ReshapeException(
slice._lengths.dup,
slice._strides.dup,
ret. _lengths.dup,
"structure is incompatible with new shape");
l = r;
}
ret._strides[nj - 1] = slice._strides[oj - 1];
foreach_reverse (i; ni .. nj - 1)
ret._strides[i] = ret._lengths[i + 1] * ret._strides[i + 1];
assert((oi == slice.N) == (ni == ret.N));
}
foreach (i; Iota!(ret.N, ret.PureN))
{
ret._lengths[i] = slice._lengths[i + slice.N - ret.N];
ret._strides[i] = slice._strides[i + slice.N - ret.N];
}
ret._ptr = slice._ptr;
return ret;
}
///
@safe pure unittest
{
import std.experimental.ndslice.iteration : allReversed;
auto slice = iotaSlice(3, 4)
.allReversed
.reshape(-1, 3);
assert(slice ==
[[11, 10, 9],
[ 8, 7, 6],
[ 5, 4, 3],
[ 2, 1, 0]]);
}
/// Reshaping with memory allocation
pure unittest
{
import std.experimental.ndslice.slice;
import std.experimental.ndslice.iteration : reversed;
import std.array : array;
auto reshape2(S, L...)(S slice, L lengths)
{
// Tries to reshape without allocation
try return slice.reshape(lengths);
catch (ReshapeException e)
//allocates the elements and creates a slice
//Note: -1 length is not supported by reshape2
return slice.byElement.array.sliced(lengths);
}
auto slice =
[0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11]
.sliced(3, 4)
.reversed!0;
assert(reshape2(slice, 4, 3) ==
[[ 8, 9, 10],
[11, 4, 5],
[ 6, 7, 0],
[ 1, 2, 3]]);
}
@safe pure unittest
{
import std.experimental.ndslice.iteration : allReversed;
auto slice = iotaSlice(1, 1, 3, 2, 1, 2, 1).allReversed;
assert(slice.reshape(1, -1, 1, 1, 3, 1) ==
[[[[[[11], [10], [9]]]],
[[[[ 8], [ 7], [6]]]],
[[[[ 5], [ 4], [3]]]],
[[[[ 2], [ 1], [0]]]]]]);
}
// Issue 15919
unittest
{
assert(iotaSlice(3, 4, 5, 6, 7).pack!2.reshape(4, 3, 5)[0, 0, 0].shape == cast(size_t[2])[6, 7]);
}
@safe pure unittest
{
import std.experimental.ndslice.slice;
import std.range : iota;
import std.exception : assertThrown;
auto e = 1.iotaSlice(1);
// resize to the wrong dimension
assertThrown!ReshapeException(e.reshape(2));
e.popFront;
// test with an empty slice
assertThrown!ReshapeException(e.reshape(1));
}
unittest
{
auto pElements = iotaSlice(3, 4, 5, 6, 7)
.pack!2
.byElement();
assert(pElements[0][0] == iotaSlice(7));
assert(pElements[$-1][$-1] == iotaSlice([7], 2513));
}
/// See_also: $(LREF reshape)
class ReshapeException: SliceException
{
/// Old lengths
size_t[] lengths;
/// Old strides
sizediff_t[] strides;
/// New lengths
size_t[] newLengths;
///
this(
size_t[] lengths,
sizediff_t[] strides,
size_t[] newLengths,
string msg,
string file = __FILE__,
uint line = cast(uint)__LINE__,
Throwable next = null
) pure nothrow @nogc @safe
{
super(msg, file, line, next);
this.lengths = lengths;
this.strides = strides;
this.newLengths = newLengths;
}
}
/++
Returns a random access range of all elements of a slice.
The order of elements is preserved.
`byElement` can be generalized with other selectors.
Params:
N = dimension count
slice = slice to be iterated
Returns:
random access range composed of elements of the `slice`
+/
auto byElement(size_t N, Range)(auto ref Slice!(N, Range) slice)
{
with (Slice!(N, Range))
{
/++
ByElement shifts the range's `_ptr` without modifying its strides and lengths.
+/
static struct ByElement
{
This _slice;
size_t _length;
size_t[N] _indexes;
static if (canSave!PureRange)
auto save() @property
{
return typeof(this)(_slice.save, _length, _indexes);
}
bool empty() const @property
{
pragma(inline, true);
return _length == 0;
}
size_t length() const @property
{
pragma(inline, true);
return _length;
}
auto ref front() @property
{
assert(!this.empty);
static if (N == PureN)
{
return _slice._ptr[0];
}
else with (_slice)
{
alias M = DeepElemType.PureN;
return DeepElemType(_lengths[$ - M .. $], _strides[$ - M .. $], _ptr);
}
}
static if (PureN == 1 && isMutable!DeepElemType && !hasAccessByRef)
auto front(E)(E elem) @property
{
assert(!this.empty);
return _slice._ptr[0] = elem;
}
void popFront()
{
assert(_length != 0);
_length--;
popFrontImpl;
}
private void popFrontImpl()
{
foreach_reverse (i; Iota!(0, N)) with (_slice)
{
_ptr += _strides[i];
_indexes[i]++;
if (_indexes[i] < _lengths[i])
return;
debug (ndslice) assert(_indexes[i] == _lengths[i]);
_ptr -= _lengths[i] * _strides[i];
_indexes[i] = 0;
}
}
auto ref back() @property
{
assert(!this.empty);
return opIndex(_length - 1);
}
static if (PureN == 1 && isMutable!DeepElemType && !hasAccessByRef)
auto back(E)(E elem) @property
{
assert(!this.empty);
return opIndexAssign(_length - 1, elem);
}
void popBack()
{
pragma(inline, true);
assert(_length != 0);
_length--;
}
void popFrontExactly(size_t n)
in
{
assert(n <= _length);
}
body
{
_length -= n;
//calculates shift and new indexes
sizediff_t _shift;
n += _indexes[N-1];
foreach_reverse (i; Iota!(1, N)) with (_slice)
{
immutable v = n / _lengths[i];
n %= _lengths[i];
_shift += (n - _indexes[i]) * _strides[i];
_indexes[i] = n;
n = _indexes[i - 1] + v;
}
assert(n <= _slice._lengths[0]);
with (_slice)
{
_shift += (n - _indexes[0]) * _strides[0];
_indexes[0] = n;
}
_slice._ptr += _shift;
}
void popBackExactly(size_t n)
in
{
assert(n <= _length);
}
body
{
pragma(inline, true);
_length -= n;
}
//calculates shift for index n
private sizediff_t getShift(size_t n)
in
{
assert(n < _length);
}
body
{
sizediff_t _shift;
n += _indexes[N-1];
foreach_reverse (i; Iota!(1, N)) with (_slice)
{
immutable v = n / _lengths[i];
n %= _lengths[i];
_shift += (n - _indexes[i]) * _strides[i];
n = _indexes[i - 1] + v;
}
debug (ndslice) assert(n < _slice._lengths[0]);
with (_slice)
_shift += (n - _indexes[0]) * _strides[0];
return _shift;
}
auto ref opIndex(size_t index)
{
static if (N == PureN)
{
return _slice._ptr[getShift(index)];
}
else with (_slice)
{
alias M = DeepElemType.PureN;
return DeepElemType(_lengths[$ - M .. $], _strides[$ - M .. $], _ptr + getShift(index));
}
}
static if (PureN == 1 && isMutable!DeepElemType && !hasAccessByRef)
auto opIndexAssign(E)(E elem, size_t index)
{
static if (N == PureN)
{
return _slice._ptr[getShift(index)] = elem;
}
else
{
static assert(0,
"ByElement.opIndexAssign is not implemented for packed slices."
~ "Use additional empty slicing `elemsOfSlice[index][] = value`"
~ tailErrorMessage());
}
}
static if (isMutable!DeepElemType && N == PureN)
{
auto opIndexAssign(V)(V val, _Slice slice)
{
return this[slice][] = val;
}
auto opIndexAssign(V)(V val)
{
foreach (ref e; this)
e = val;
return this;
}
auto opIndexAssign(V : T[], T)(V val)
if (__traits(compiles, front = val[0]))
{
assert(_length == val.length, "lengths should be equal" ~ tailErrorMessage!());
foreach (ref e; this)
{
e = val[0];
val = val[1 .. $];
}
return this;
}
auto opIndexAssign(V : Slice!(1, _Range), _Range)(V val)
if (__traits(compiles, front = val.front))
{
assert(_length == val.length, "lengths should be equal" ~ tailErrorMessage!());
foreach (ref e; this)
{
e = val.front;
val.popFront;
}
return this;
}
}
auto opIndex(_Slice sl)
{
auto ret = this;
ret.popFrontExactly(sl.i);
ret.popBackExactly(_length - sl.j);
return ret;
}
alias opDollar = length;
_Slice opSlice(size_t pos : 0)(size_t i, size_t j)
in
{
assert(i <= j,
"the left bound must be less than or equal to the right bound"
~ tailErrorMessage!());
assert(j - i <= _length,
"the difference between the right and the left bound must be less than or equal to range length"
~ tailErrorMessage!());
}
body
{
pragma(inline, true);
return typeof(return)(i, j);
}
size_t[N] index() @property
{
pragma(inline, true);
return _indexes;
}
}
return ByElement(slice, slice.elementsCount);
}
}
/// Regular slice
@safe @nogc pure nothrow unittest
{
import std.algorithm.comparison : equal;
import std.range : iota;
assert(iotaSlice(4, 5)
.byElement
.equal(20.iota));
}
/// Packed slice
@safe @nogc pure nothrow unittest
{
import std.experimental.ndslice.slice;
import std.experimental.ndslice.iteration;
import std.range : drop;
assert(iotaSlice(3, 4, 5, 6, 7)
.pack!2
.byElement()
.drop(1)
.front
== iotaSlice([6, 7], 6 * 7));
}
/// Properties
pure nothrow unittest
{
auto elems = iotaSlice(3, 4).byElement;
elems.popFrontExactly(2);
assert(elems.front == 2);
assert(elems.index == [0, 2]);
elems.popBackExactly(2);
assert(elems.back == 9);
assert(elems.length == 8);
}
/// Index property
pure nothrow unittest
{
import std.experimental.ndslice.slice;
auto slice = new long[20].sliced(5, 4);
for (auto elems = slice.byElement; !elems.empty; elems.popFront)
{
size_t[2] index = elems.index;
elems.front = index[0] * 10 + index[1] * 3;
}
assert(slice ==
[[ 0, 3, 6, 9],
[10, 13, 16, 19],
[20, 23, 26, 29],
[30, 33, 36, 39],
[40, 43, 46, 49]]);
}
pure nothrow unittest
{
// test save
import std.range : dropOne;
import std.range : iota;
auto elems = 12.iota.sliced(3, 4).byElement;
assert(elems.front == 0);
assert(elems.save.dropOne.front == 1);
assert(elems.front == 0);
}
/++
Random access and slicing
+/
@nogc nothrow unittest
{
import std.experimental.ndslice.slice;
import std.algorithm.comparison : equal;
import std.array : array;
import std.range : iota, repeat;
static data = 20.iota.array;
auto elems = data.sliced(4, 5).byElement;
elems = elems[11 .. $ - 2];
assert(elems.length == 7);
assert(elems.front == 11);
assert(elems.back == 17);
foreach (i; 0 .. 7)
assert(elems[i] == i + 11);
// assign an element
elems[2 .. 6] = -1;
assert(elems[2 .. 6].equal(repeat(-1, 4)));
// assign an array
static ar = [-1, -2, -3, -4];
elems[2 .. 6] = ar;
assert(elems[2 .. 6].equal(ar));
// assign a slice
ar[] *= 2;
auto sl = ar.sliced(ar.length);
elems[2 .. 6] = sl;
assert(elems[2 .. 6].equal(sl));
}
/++
Forward access works faster than random access or backward access.
Use $(SUBREF iteration, allReversed) in pipeline before
`byElement` to achieve fast backward access.
+/
@safe @nogc pure nothrow unittest
{
import std.range : retro;
import std.experimental.ndslice.iteration : allReversed;
auto slice = iotaSlice(3, 4, 5);
/// Slow backward iteration #1
foreach (ref e; slice.byElement.retro)
{
//...
}
/// Slow backward iteration #2
foreach_reverse (ref e; slice.byElement)
{
//...
}
/// Fast backward iteration
foreach (ref e; slice.allReversed.byElement)
{
//...
}
}
@safe @nogc pure nothrow unittest
{
import std.range.primitives : isRandomAccessRange, hasSlicing;
auto elems = iotaSlice(4, 5).byElement;
static assert(isRandomAccessRange!(typeof(elems)));
static assert(hasSlicing!(typeof(elems)));
}
// Checks strides
@safe @nogc pure nothrow unittest
{
import std.experimental.ndslice.iteration;
import std.range : isRandomAccessRange;
auto elems = iotaSlice(4, 5).everted.byElement;
static assert(isRandomAccessRange!(typeof(elems)));
elems = elems[11 .. $ - 2];
auto elems2 = elems;
foreach (i; 0 .. 7)
{
assert(elems[i] == elems2.front);
elems2.popFront;
}
}
@safe @nogc pure nothrow unittest
{
import std.experimental.ndslice.slice;
import std.experimental.ndslice.iteration;
import std.range : iota, isForwardRange, hasLength;
import std.algorithm.comparison : equal;
auto range = (3 * 4 * 5 * 6 * 7).iota;
auto slice0 = range.sliced(3, 4, 5, 6, 7);
auto slice1 = slice0.transposed!(2, 1).pack!2;
auto elems0 = slice0.byElement;
auto elems1 = slice1.byElement;
import std.meta;
foreach (S; AliasSeq!(typeof(elems0), typeof(elems1)))
{
static assert(isForwardRange!S);
static assert(hasLength!S);
}
assert(elems0.length == slice0.elementsCount);
assert(elems1.length == 5 * 4 * 3);
auto elems2 = elems1;
foreach (q; slice1)
foreach (w; q)
foreach (e; w)
{
assert(!elems2.empty);
assert(e == elems2.front);
elems2.popFront;
}
assert(elems2.empty);
elems0.popFront();
elems0.popFrontExactly(slice0.elementsCount - 14);
assert(elems0.length == 13);
assert(elems0.equal(range[slice0.elementsCount - 13 .. slice0.elementsCount]));
foreach (elem; elems0) {}
}
// Issue 15549
unittest
{
import std.range.primitives;
alias A = typeof(iotaSlice(2, 5).sliced(1, 1, 1, 1));
static assert(isRandomAccessRange!A);
static assert(hasLength!A);
static assert(hasSlicing!A);
alias B = typeof(slice!double(2, 5).sliced(1, 1, 1, 1));
static assert(isRandomAccessRange!B);
static assert(hasLength!B);
static assert(hasSlicing!B);
}
// Issue 16010
unittest
{
auto s = iotaSlice(3, 4).byElement;
foreach (_; 0 .. s.length)
s = s[1 .. $];
}
/++
Returns an forward range of all elements of standard simplex of a slice.
In case the slice has two dimensions, it is composed of elements of upper left triangular matrix.
The order of elements is preserved.
`byElementInStandardSimplex` can be generalized with other selectors.
Params:
N = dimension count
slice = slice to be iterated
Returns:
forward range composed of all elements of standard simplex of the `slice`
+/
auto byElementInStandardSimplex(size_t N, Range)(auto ref Slice!(N, Range) slice, size_t maxCobeLength = size_t.max)
{
with (Slice!(N, Range))
{
/++
ByElementInTopSimplex shifts the range's `_ptr` without modifying its strides and lengths.
+/
static struct ByElementInTopSimplex
{
This _slice;
size_t _length;
size_t maxCobeLength;
size_t sum;
size_t[N] _indexes;
static if (canSave!PureRange)
auto save() @property
{
return typeof(this)(_slice.save, _length, maxCobeLength, sum, _indexes);
}
bool empty() const @property
{
pragma(inline, true);
return _length == 0;
}
size_t length() const @property
{
pragma(inline, true);
return _length;
}
auto ref front() @property
{
assert(!this.empty);
static if (N == PureN)
return _slice._ptr[0];
else with (_slice)
{
alias M = DeepElemType.PureN;
return DeepElemType(_lengths[$ - M .. $], _strides[$ - M .. $], _ptr);
}
}
static if (PureN == 1 && isMutable!DeepElemType && !hasAccessByRef)
auto front(E)(E elem) @property
{
pragma(inline, true);
assert(!this.empty);
return _slice._ptr[0] = elem;
}
void popFront()
{
pragma(inline, true);
assert(_length != 0);
_length--;
popFrontImpl;
}
private void popFrontImpl()
{
foreach_reverse (i; Iota!(0, N)) with (_slice)
{
_ptr += _strides[i];
_indexes[i]++;
debug (ndslice) assert(_indexes[i] <= _lengths[i]);
sum++;
if (sum < maxCobeLength)
return;
debug (ndslice) assert(sum == maxCobeLength);
_ptr -= _indexes[i] * _strides[i];
sum -= _indexes[i];
_indexes[i] = 0;
}
}
size_t[N] index() @property
{
pragma(inline, true);
return _indexes;
}
}
foreach (i; Iota!(0, N))
if (maxCobeLength > slice._lengths[i])
maxCobeLength = slice._lengths[i];
immutable size_t elementsCount = ((maxCobeLength + 1) * maxCobeLength ^^ (N - 1)) / 2;
return ByElementInTopSimplex(slice, elementsCount, maxCobeLength);
}
}
///
pure nothrow unittest
{
import std.experimental.ndslice.slice;
auto slice = slice!int(4, 5);
auto elems = slice
.byElementInStandardSimplex;
int i;
foreach (ref e; elems)
e = ++i;
assert(slice ==
[[ 1, 2, 3, 4, 0],
[ 5, 6, 7, 0, 0],
[ 8, 9, 0, 0, 0],
[10, 0, 0, 0, 0]]);
}
///
pure nothrow unittest
{
import std.experimental.ndslice.slice;
import std.experimental.ndslice.iteration;
auto slice = slice!int(4, 5);
auto elems = slice
.transposed
.allReversed
.byElementInStandardSimplex;
int i;
foreach (ref e; elems)
e = ++i;
assert(slice ==
[[0, 0, 0, 0, 4],
[0, 0, 0, 7, 3],
[0, 0, 9, 6, 2],
[0, 10, 8, 5, 1]]);
}
/// Properties
@safe @nogc pure nothrow unittest
{
import std.range.primitives : popFrontN;
auto elems = iotaSlice(3, 4).byElementInStandardSimplex;
elems.popFront;
assert(elems.front == 1);
assert(elems.index == cast(size_t[2])[0, 1]);
elems.popFrontN(3);
assert(elems.front == 5);
}
/// Save
@safe @nogc pure nothrow unittest
{
auto elems = iotaSlice(3, 4).byElementInStandardSimplex;
import std.range : dropOne, popFrontN;
elems.popFrontN(4);
assert(elems.save.dropOne.front == 8);
assert(elems.front == 5);
assert(elems.index == cast(size_t[2])[1, 1]);
assert(elems.length == 2);
}
/++
Returns a slice, the elements of which are equal to the initial multidimensional index value.
This is multidimensional analog of $(REF iota, std, range).
For a flattened (continuous) index, see $(LREF iotaSlice).
Params:
N = dimension count
lengths = list of dimension lengths
Returns:
`N`-dimensional slice composed of indexes
See_also: $(LREF IndexSlice), $(LREF iotaSlice)
+/
IndexSlice!(Lengths.length) indexSlice(Lengths...)(Lengths lengths)
if (allSatisfy!(isIndex, Lengths))
{
return .indexSlice!(Lengths.length)([lengths]);
}
///ditto
IndexSlice!N indexSlice(size_t N)(auto ref size_t[N] lengths)
{
import std.experimental.ndslice.slice : sliced;
with (typeof(return)) return Range(lengths[1 .. $]).sliced(lengths);
}
///
@safe pure nothrow @nogc unittest
{
auto slice = indexSlice(2, 3);
static immutable array =
[[[0, 0], [0, 1], [0, 2]],
[[1, 0], [1, 1], [1, 2]]];
assert(slice == array);
static assert(is(IndexSlice!2 : Slice!(2, Range), Range));
static assert(is(DeepElementType!(IndexSlice!2) == size_t[2]));
}
///
@safe pure nothrow unittest
{
auto im = indexSlice(7, 9);
assert(im[2, 1] == [2, 1]);
//slicing works correctly
auto cm = im[1 .. $, 4 .. $];
assert(cm[2, 1] == [3, 5]);
}
@safe pure nothrow unittest
{
// test save
import std.range : dropOne;
auto im = indexSlice(7, 9);
auto imByElement = im.byElement;
assert(imByElement.front == [0, 0]);
assert(imByElement.save.dropOne.front == [0, 1]);
assert(imByElement.front == [0, 0]);
}
/++
Slice composed of indexes.
See_also: $(LREF indexSlice)
+/
template IndexSlice(size_t N)
if (N)
{
struct IndexMap
{
private size_t[N-1] _lengths;
IndexMap save() @property const
{
pragma(inline, true);
return this;
}
size_t[N] opIndex(size_t index) const
{
pragma(inline, true);
size_t[N] indexes = void;
foreach_reverse (i; Iota!(0, N - 1))
{
indexes[i + 1] = index % _lengths[i];
index /= _lengths[i];
}
indexes[0] = index;
return indexes;
}
}
alias IndexSlice = Slice!(N, IndexMap);
}
unittest
{
auto r = indexSlice(1);
import std.range.primitives : isRandomAccessRange;
static assert(isRandomAccessRange!(typeof(r)));
}
/++
Returns a slice, the elements of which are equal to the initial flattened index value.
For a multidimensional index, see $(LREF indexSlice).
Params:
N = dimension count
lengths = list of dimension lengths
shift = value of the first element in a slice
Returns:
`N`-dimensional slice composed of indexes
See_also: $(LREF IotaSlice), $(LREF indexSlice)
+/
IotaSlice!(Lengths.length) iotaSlice(Lengths...)(Lengths lengths)
if (allSatisfy!(isIndex, Lengths))
{
return .iotaSlice!(Lengths.length)([lengths]);
}
///ditto
IotaSlice!N iotaSlice(size_t N)(auto ref size_t[N] lengths, size_t shift = 0)
{
import std.experimental.ndslice.slice : sliced;
with (typeof(return)) return Range.init.sliced(lengths, shift);
}
///
@safe pure nothrow @nogc unittest
{
auto slice = iotaSlice(2, 3);
static immutable array =
[[0, 1, 2],
[3, 4, 5]];
assert(slice == array);
import std.range.primitives : isRandomAccessRange;
static assert(isRandomAccessRange!(IotaSlice!2));
static assert(is(IotaSlice!2 : Slice!(2, Range), Range));
static assert(is(DeepElementType!(IotaSlice!2) == size_t));
}
///
@safe pure nothrow unittest
{
auto im = iotaSlice([10, 5], 100);
assert(im[2, 1] == 111); // 100 + 2 * 5 + 1
//slicing works correctly
auto cm = im[1 .. $, 3 .. $];
assert(cm[2, 1] == 119); // 119 = 100 + (1 + 2) * 5 + (3 + 1)
}
/++
Slice composed of flattened indexes.
See_also: $(LREF iotaSlice)
+/
template IotaSlice(size_t N)
if (N)
{
alias IotaSlice = Slice!(N, IotaMap!());
}
// undocumented
// zero cost variant of `std.range.iota`
struct IotaMap()
{
enum bool empty = false;
enum IotaMap save = IotaMap.init;
static size_t opIndex()(size_t index) @safe pure nothrow @nogc @property
{
pragma(inline, true);
return index;
}
}