phobos/std/experimental/ndslice/selection.d
Ilya Yaroshenko 54a6d72bb8 initial commit
fix Slice

change concept

implement `sliced`

change structure

remove comment

slice constructors

add 1D case

fix opSlice 1D

implement ND, part1

update to modern syntax

generic opIndex

fix protection

cleanup

fix

cleanup

style fix

rename

update

update

implement transpose

unittest

unittest for properties and methods

more examples & fixes

move code

minor fix

add header

update make fils

update make files

dmd bug workaround

fix module name

update imports

style fix

fix asserts

remove createRefCountedSlice

add shape property

rework `shape`, add `structrure`

ndarray

swapped

transposed

opCast

remove save calls

everted

make Slice(size_t N0, Slice(size_t N1, ...)) virtual

huge rework

remove space

move code

fix style

add packEverted

relax constraints

add black comments

remove spaces

fix macros

fix doc

fix docs

update docs

reduce template bloat

optimize and fix for arrays.

update docs

remove space

update docs

update docs

update link

update doc

fix constructor

add toString

fix `save`

add `reversed` method

fix constraints for `reversed`

optimisation in unittests

add `strided`

implement `reversed` for ranges (no arrays)

reduce constraints

cleanup

add Iota for static foreach

remove string representation

DMD 2.067 support

fix style

fix opIndexUnary constraints

move byElement

add shift property

add CTFE-able strided

huge style fix

add macros

update `sliced`

move private code

update docs

update allIndexesReversed, renamed to allReversed

update docs

update docs

fix macros

fix posix.mak

update posix.mak

update docs and strided

move code

update sliced constraints

update `sliced` docs

move code

remove whitespaces

add static assert to ndslice

add one more opIndexUnary

update createSlice docs

update ndarray

update docs for Transpose operators

update docs and fix bugs

add pragma inline

fix docs

update docs

update docs

add inline pragma

ditto

replace errors with asserts

update docs

update doc style

update docs

remove comes in docs

update docs

remove whitespaces

update docs

update docs

update comment

update test

update docs

fix typo

fix docs

review

fix transposed description

change doc tables

remove unused RepeatTypeTuple

remove function attributes for templates

make constructor private

make N and Range private

make elementsCount public

fix createSlice params naming

add assert description to sliced

[big] fix range primitives wrong asserts and update documentation

regroup primitives

minor docs fix

minor docs fix

fix typo

fix Slice constraints

add indexing using static arrays

make byElement a random access range

fix by random access primitives for ByElement

update unittest

fix random access primitives for ByElement

remove trailing space

implement slicing for ByElement

make ByElement more readable

update docs for subspace operators

remove one See_also

revert last commit

update docs

add descriptions to asserts

add more examples

minor doc update

add example with allocators

add makeSlice for allocators

update docs table

add range checks

add more constructors

Add description to asserts.

add checks for const/immutable

ditto

update to DMD 2.069

minor fixes

add elements property

make makeSlice an unittest

remove space

update docs

remove space

update docs

update doc

fix makeSlice example

fix strided

make strided template variadic

add Guide for Slice/Matrix/BLAS contributers

remove unused import

add better error messages

update guide

update docs

remove space

[minor] fix docs

minor error messages update

minor doc fix

rename package

split package

update posix.mak

update win*.mak

ditto

fix posix mak

update *mak.

update docs

update man files

minor doc update

update module headers

fix opCast

rename pop*N to pop*Exactly

remove primitives

add popFrontN popBackN to Slice

[minor] update docs

add package primitives to Slice

update operators prototypes

add drop* primitives

[minor] update docs

update docs

remove spaces

remove allocators

minor doc fix

[minor] update docs

add dropToNCube

add diagonal

add return type for template for better documentation

move pack* to iterators

rm allocators

[minor] doc update

add support of packed slice for diagonal

update example for diagonal

add blocks

rename packed and unpacked to pack and unpack

update docs

renaming

[minor] docs update

ditto

minor style update

rm old files

[minor] update docs

update docs

ditto

[minor] update docs

add rotated

[minor] update docs

add byElementInStandardSimplex

add windows

remove space

remove structure

update docs

add spaces

add reshape

rename packEverted -> evertPack

remove spaces

fix ReshapeException

minor doc fix

update windows/blocks template params

fix pack windows/blocks

add index @property

remove spaces

minor doc fix

add Slice tuples

remove spaces

update docs

update docs and rename dropToHypercube

update docs

minor doc update

fix links

remove version OS constraints for allocators

assumeSameStructure

fix

minor doc fix

minor doc update

after review

minor style fix

fix Elaborate Assign

add index slice

fix NSeq

fix bug with diagonal

fix sliced slice

add main example

update docs

translation

fix comment

fix style

remove spaces

update style

fix link

Vandermonde matrix

remove space

move example

remove `opCast`

add opEquals for arrays

update opIndex(Op)Assign

update docs

update docs

fix style

update docs (russian will be translated)

update tests

fix doc style

ditto

ditto

update docs

update docs

update docs

update docs

update unittests

update docs

ditto

ditto

ditto

[major] doc update

update docs

update docs

fix voting conditions (docs, style)

minor doc update

fix style

add unittest for `map`

ditto

fix string mixins

add Params and Returns

ditto

add headers

add descriptions

Fix m32 mode in example

Minor description fix

fix spaces

ditto

Add Internal Binary Representation

ditto

ditto

ditto

ditto

ditto

ditto

ditto

add description for binary representation

ditto

minor style fix

ditto

ditto

ditto

ditto

ditto

ditto

inlining

remove compiler version check

fix braces

fix docs

add Quick_Start

Fix English

Add two examples

fix style

remove object file

minor doc update

ditto

remove spaces

fix indexing & add unittests

ditto
2016-01-01 23:23:50 -08:00

1524 lines
41 KiB
D

/**
$(SCRIPT inhibitQuickIndex = 1;)
This is a submodule of $(LINK2 std_experimental_ndslice.html, 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, a 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, returns a slice with elements equal to the initial index)
$(T2 reshape, returns a new slice 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:
SUBMODULE = $(LINK2 std_experimental_ndslice_$1.html, std.experimental.ndslice.$1)
SUBREF = $(LINK2 std_experimental_ndslice_$1.html#.$2, $(TT $2))$(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.pacKed!K1.pacKed!K2. ... pacKed!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;
import std.algorithm.comparison: equal;
auto r = 100000000.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
{
import std.experimental.ndslice.slice;
import std.experimental.ndslice.selection;
import std.range: iota;
auto r = 100000000.iota;
auto a = r.sliced(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);
}
/++
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
{
import std.experimental.ndslice.slice;
import std.range: iota;
import std.algorithm.comparison: equal;
auto r = 100000000.iota;
auto a = r.sliced(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.slice;
import std.experimental.ndslice.iteration: transposed;
import std.range: iota;
auto slice = 100000000.iota.sliced(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 = 100000000.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(new int[20]
.sliced(20)
.evertPack)
== Slice!(1LU, int*)));
static assert(is(typeof(new int[20]
.sliced(20)
.sliced(3)
.evertPack)
== Slice!(2LU, int*)));
static assert(is(typeof(new int[20]
.sliced(20)
.sliced(1,2,3)
.sliced(3)
.evertPack)
== Slice!(3LU, Slice!(2LU, int*))));
static assert(is(typeof(new int[20]
.sliced(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:
packed `1`-dimensional composed of `N`-dimensional slices
+/
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
{
import std.experimental.ndslice.slice;
import std.algorithm.comparison: equal;
import std.range: iota, only;
// -------
// | 0 1 2 |
// | 3 4 5 |
// -------
//->
// | 0 4 |
assert(10.iota
.sliced(2, 3)
.diagonal
.equal(only(0, 4)));
}
/// ditto
pure nothrow unittest
{
import std.experimental.ndslice.slice;
auto slice = new int[9].sliced(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.slice;
import std.experimental.ndslice.iteration: dropOne;
import std.algorithm.comparison: equal;
import std.range: iota, only;
// -------
// | 0 1 2 |
// | 3 4 5 |
// -------
//->
// | 1 5 |
assert(10.iota
.sliced(2, 3)
.dropOne!1
.diagonal
.equal(only(1, 5)));
}
/// Matrix, antidiagonal
@safe @nogc pure nothrow unittest
{
import std.experimental.ndslice.slice;
import std.experimental.ndslice.iteration: dropToHypercube, reversed;
import std.algorithm.comparison: equal;
import std.range: iota, only;
// -------
// | 0 1 2 |
// | 3 4 5 |
// -------
//->
// | 1 3 |
assert(10.iota
.sliced(2, 3)
.dropToHypercube
.reversed!1
.diagonal
.equal(only(1, 3)));
}
/// 3D, main diagonal
@safe @nogc pure nothrow unittest
{
import std.experimental.ndslice.slice;
import std.algorithm.comparison: equal;
import std.range: iota, only;
// -----------
// | 0 1 2 |
// | 3 4 5 |
// - - - - - -
// | 6 7 8 |
// | 9 10 11 |
// -----------
//->
// | 0 10 |
assert(100.iota
.sliced(2, 2, 3)
.diagonal
.equal(only(0, 10)));
}
/// 3D, subdiagonal
@safe @nogc pure nothrow unittest
{
import std.experimental.ndslice.slice;
import std.experimental.ndslice.iteration: dropOne;
import std.algorithm.comparison: equal;
import std.range: iota, only;
// -----------
// | 0 1 2 |
// | 3 4 5 |
// - - - - - -
// | 6 7 8 |
// | 9 10 11 |
// -----------
//->
// | 1 11 |
assert(100.iota
.sliced(2, 2, 3)
.dropOne!2
.diagonal
.equal(only(1, 11)));
}
/// 3D, diagonal plain
@safe pure nothrow unittest
{
import std.experimental.ndslice.slice;
import std.experimental.ndslice.iteration: dropOne;
import std.algorithm.comparison: equal;
import std.range: iota;
// -----------
// | 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 |
// -----------
auto slice = 100.iota
.sliced(3, 3, 3)
.pack!2
.evertPack
.diagonal
.evertPack;
assert(slice ==
[[ 0, 4, 8],
[ 9, 13, 17],
[18, 22, 26]]);
}
/++
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 = new int[1000].sliced(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 = new int[1000].sliced(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 = new int[1000].sliced(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 = new int[1000].sliced(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 = new int[1000].sliced(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 = new int[1000].sliced(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 = new int[1000].sliced(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.
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 + slcie.N - ret.N];
ret._strides[i] = slice._strides[i + slcie.N - ret.N];
}
ret._ptr = slice._ptr;
return ret;
}
///
@safe pure unittest
{
import std.experimental.ndslice.slice;
import std.range: iota;
import std.experimental.ndslice.iteration: allReversed;
auto slice = 100.iota
.sliced(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.slice;
import std.experimental.ndslice.iteration: allReversed;
import std.stdio;
import std.range: iota;
auto slice = 100.iota.sliced(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]]]]]]);
}
/// See_also: $(LREF reshape)
class ReshapeException: Exception
{
/// 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 && rangeHasMutableElements && !hasAccessByRef)
auto front(DeepElemType 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 && rangeHasMutableElements && !hasAccessByRef)
auto back(DeepElemType 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)
{
return _slice._ptr[getShift(index)];
}
static if (PureN == 1 && rangeHasMutableElements && !hasAccessByRef)
auto opIndexAssign(DeepElemType elem, size_t index)
{
return _slice[getShift(index)] = elem;
}
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.experimental.ndslice.slice;
import std.algorithm.comparison: equal;
import std.range: iota;
assert(100.iota
.sliced(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: iota, drop;
import std.algorithm.comparison: equal;
assert(100000.iota
.sliced(3, 4, 5, 6, 7)
.pack!2
.byElement()
.drop(1)
.front
.byElement
.equal(iota(6 * 7, 6 * 7 * 2)));
}
/// Properties
pure nothrow unittest
{
import std.experimental.ndslice.slice;
import std.range: iota;
auto elems = 12.iota.sliced(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]]);
}
/++
Random access and slicing
+/
@safe @nogc pure nothrow unittest
{
import std.experimental.ndslice.slice;
import std.range: iota;
auto elems = 100.iota.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);
}
/++
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, iota;
import std.experimental.ndslice.iteration: allReversed;
auto slice = 100.iota.sliced(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.experimental.ndslice.slice;
import std.range: iota, isRandomAccessRange, hasSlicing;
auto elems = 100.iota.sliced(4, 5).byElement;
static assert(isRandomAccessRange!(typeof(elems)));
static assert(hasSlicing!(typeof(elems)));
}
// Checks strides
@safe @nogc pure nothrow unittest
{
import std.experimental.ndslice.slice;
import std.experimental.ndslice.iteration;
import std.range: iota, isRandomAccessRange;
auto elems = 100.iota.sliced(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 = 100000.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) {}
}
/++
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 && rangeHasMutableElements && !hasAccessByRef)
auto front(DeepElemType 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 = new int[20].sliced(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 = new int[20].sliced(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.experimental.ndslice.slice;
import std.range: iota;
auto elems = 12.iota.sliced(3, 4).byElementInStandardSimplex;
elems.popFront;
assert(elems.front == 1);
assert(elems.index == cast(size_t[2])[0, 1]);
import std.range: popFrontN;
elems.popFrontN(3);
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 index value.
Params:
N = dimension count
lengths = list of dimension lengths
Returns:
`N`-dimensional slice composed of indexes
See_also: $(LREF IndexSlice)
+/
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 @nogc pure nothrow unittest
{
auto im = indexSlice(7, 9);
assert(im[2, 1] == cast(size_t[2])[2, 1]);
for (auto elems = im.byElement; !elems.empty; elems.popFront)
assert(elems.front == elems.index);
//slicing works correctly
auto cm = im[1 .. $ - 3, 4 .. $ - 1];
assert(cm[2, 1] == cast(size_t[2])[3, 5]);
}
/++
Slice composed of indexes.
See_also: $(LREF indexSlice)
+/
template IndexSlice(size_t N)
if (N)
{
struct IndexMap
{
private size_t[N-1] _lengths;
auto 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);
}
///
@safe @nogc pure nothrow unittest
{
alias IS4 = IndexSlice!4;
static assert(is(IS4 == Slice!(4, Range), Range));
}
unittest
{
auto r = indexSlice(1);
}