From 54a6d72bb8875cfc5d0d6df6fcf20242c95b9749 Mon Sep 17 00:00:00 2001 From: Ilya Yaroshenko Date: Tue, 9 Jun 2015 20:10:12 +0600 Subject: [PATCH] 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 --- posix.mak | 6 +- std/experimental/ndslice/internal.d | 194 ++ std/experimental/ndslice/iteration.d | 1222 ++++++++++++ std/experimental/ndslice/package.d | 562 ++++++ std/experimental/ndslice/selection.d | 1524 +++++++++++++++ std/experimental/ndslice/slice.d | 2654 ++++++++++++++++++++++++++ win32.mak | 11 +- win64.mak | 11 +- 8 files changed, 6181 insertions(+), 3 deletions(-) create mode 100644 std/experimental/ndslice/internal.d create mode 100644 std/experimental/ndslice/iteration.d create mode 100644 std/experimental/ndslice/package.d create mode 100644 std/experimental/ndslice/selection.d create mode 100644 std/experimental/ndslice/slice.d diff --git a/posix.mak b/posix.mak index 5e25eb7e4b5..f3cb7c15ac2 100644 --- a/posix.mak +++ b/posix.mak @@ -152,7 +152,9 @@ P2MODULES=$(foreach P,$1,$(addprefix $P/,$(PACKAGE_$(subst /,_,$P)))) # packages and their modules. STD_PACKAGES = std $(addprefix std/,\ algorithm container digest experimental/allocator \ - experimental/allocator/building_blocks experimental/logger net \ + experimental/allocator/building_blocks experimental/logger \ + experimental/ndslice \ + net \ range regex) # Modules broken down per package @@ -176,6 +178,7 @@ PACKAGE_std_experimental_allocator_building_blocks = \ fallback_allocator free_list free_tree bitmapped_block \ kernighan_ritchie null_allocator package quantizer \ region scoped_allocator segregator stats_collector +PACKAGE_std_experimental_ndslice = package iteration selection slice PACKAGE_std_net = curl isemail PACKAGE_std_range = interfaces package primitives PACKAGE_std_regex = package $(addprefix internal/,generator ir parser \ @@ -209,6 +212,7 @@ EXTRA_MODULES_INTERNAL := $(addprefix \ cstring processinit unicode_tables scopebuffer\ unicode_comp unicode_decomp unicode_grapheme unicode_norm) \ $(addprefix std/internal/test/, dummyrange) \ + $(addprefix std/experimental/ndslice/, internal) \ $(addprefix std/algorithm/, internal) EXTRA_MODULES += $(EXTRA_DOCUMENTABLES) $(EXTRA_MODULES_INTERNAL) diff --git a/std/experimental/ndslice/internal.d b/std/experimental/ndslice/internal.d new file mode 100644 index 00000000000..78c186dc1ca --- /dev/null +++ b/std/experimental/ndslice/internal.d @@ -0,0 +1,194 @@ +module std.experimental.ndslice.internal; + +import std.traits; +import std.meta; //: AliasSeq, anySatisfy, Filter, Reverse; + +package: + +enum string tailErrorMessage( + string fun = __FUNCTION__, + string pfun = __PRETTY_FUNCTION__) = +" +- - - +Error in function +" ~ fun ~ " +- - - +Function prototype +" ~ pfun ~ " +_____"; + +mixin template _DefineRet() +{ + alias Ret = typeof(return); + static if (hasElaborateAssign!(Ret.PureRange)) + Ret ret; + else + Ret ret = void; +} + +mixin template DimensionsCountCTError() +{ + static assert(Dimensions.length <= N, + "Dimensions list length = " ~ Dimensions.length.stringof + ~ " should be less than or equal to N = " ~ N.stringof + ~ tailErrorMessage!()); +} + +enum DimensionsCountRTError = q{ + assert(dimensions.length <= N, + "Dimensions list length should be less than or equal to N = " ~ N.stringof + ~ tailErrorMessage!()); +}; + +mixin template DimensionCTError() +{ + static assert(dimension >= 0, + "dimension = " ~ dimension.stringof ~ " at position " + ~ i.stringof ~ " should be greater than or equal to 0" + ~ tailErrorMessage!()); + static assert(dimension < N, + "dimension = " ~ dimension.stringof ~ " at position " + ~ i.stringof ~ " should be less than N = " ~ N.stringof + ~ tailErrorMessage!()); +} + +enum DimensionRTError = q{ + static if (isSigned!(typeof(dimension))) + assert(dimension >= 0, "dimension should be greater than or equal to 0" + ~ tailErrorMessage!()); + assert(dimension < N, "dimension should be less than N = " ~ N.stringof + ~ tailErrorMessage!()); +}; + +private alias IncFront(Seq...) = AliasSeq!(Seq[0] + 1, Seq[1 .. $]); + +private alias DecFront(Seq...) = AliasSeq!(Seq[0] - 1, Seq[1 .. $]); + +private enum bool isNotZero(alias t) = t != 0; + +alias NSeqEvert(Seq...) = Filter!(isNotZero, DecFront!(Reverse!(IncFront!Seq))); + +alias Parts(Seq...) = DecAll!(IncFront!Seq); + +alias Snowball(Seq...) = AliasSeq!(size_t.init, SnowballImpl!(size_t.init, Seq)); + +private template SnowballImpl(size_t val, Seq...) +{ + static if (Seq.length == 0) + alias SnowballImpl = AliasSeq!(); + else + alias SnowballImpl = AliasSeq!(Seq[0] + val, SnowballImpl!(Seq[0] + val, Seq[1 .. $])); +} + +private template DecAll(Seq...) +{ + static if (Seq.length == 0) + alias DecAll = AliasSeq!(); + else + alias DecAll = AliasSeq!(Seq[0] - 1, DecAll!(Seq[1 .. $])); +} + +template SliceFromSeq(Range, Seq...) +{ + static if (Seq.length == 0) + alias SliceFromSeq = Range; + else + { + import std.experimental.ndslice.slice: Slice; + alias SliceFromSeq = SliceFromSeq!(Slice!(Seq[$ - 1], Range), Seq[0 .. $ - 1]); + } +} + +template DynamicArrayDimensionsCount(T) +{ + static if(isDynamicArray!T) + enum size_t DynamicArrayDimensionsCount = 1 + DynamicArrayDimensionsCount!(typeof(T.init[0])); + else + enum size_t DynamicArrayDimensionsCount = 0; +} + +bool isPermutation(size_t N)(auto ref in size_t[N] perm) +{ + int[N] mask; + if (isValidPartialPermutationImpl(perm, mask) == false) + return false; + foreach (e; mask) + if (e == false) + return false; + return true; +} + +bool isValidPartialPermutation(size_t N)(in size_t[] perm) +{ + int[N] mask; + return isValidPartialPermutationImpl(perm, mask); +} + +private bool isValidPartialPermutationImpl(size_t N)(in size_t[] perm, ref int[N] mask) +{ + if (perm.length == 0) + return false; + foreach (j; perm) + { + if (j >= N) + return false; + if (mask[j]) //duplicate + return false; + mask[j] = true; + } + return true; +} + +enum isIndex(I) = is(I : size_t); + +private enum isReference(P) = + hasIndirections!P + || isFunctionPointer!P + || is(P == interface); + +enum hasReference(T) = anySatisfy!(isReference, RepresentationTypeTuple!T); + +alias ImplicitlyUnqual(T) = Select!(isImplicitlyConvertible!(T, Unqual!T), Unqual!T, T); + +//TODO: replace with `static foreach` +template Iota(size_t i, size_t j) +{ + static assert(i <= j, "Iota: i should be less than or equal to j"); + static if (i == j) + alias Iota = AliasSeq!(); + else + alias Iota = AliasSeq!(i, Iota!(i + 1, j)); +} + +template Repeat(T, size_t N) +{ + static if (N) + alias Repeat = AliasSeq!(Repeat!(T, N - 1), T); + else + alias Repeat = AliasSeq!(); +} + +size_t lengthsProduct(size_t N)(auto ref in size_t[N] lengths) +{ + size_t length = lengths[0]; + foreach (i; Iota!(1, N)) + length *= lengths[i]; + return length; +} + +pure nothrow unittest +{ + const size_t[3] lengths = [3, 4, 5]; + assert(lengthsProduct(lengths) == 60); + assert(lengthsProduct([3, 4, 5]) == 60); +} + +enum canSave(T) = isPointer!T || isDynamicArray!T || + __traits(compiles, + { + T r1 = T.init; + auto s1 = r1.save; + static assert (is(typeof(s1) == T)); + }); + +struct _Slice { size_t i, j; } diff --git a/std/experimental/ndslice/iteration.d b/std/experimental/ndslice/iteration.d new file mode 100644 index 00000000000..bfa390b1883 --- /dev/null +++ b/std/experimental/ndslice/iteration.d @@ -0,0 +1,1222 @@ +/** +$(SCRIPT inhibitQuickIndex = 1;) + +This is a submodule of $(LINK2 std_experimental_ndslice.html, 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 Descriprottion)) +$(T2 transposed, `100000.iota.sliced(3, 4, 5, 6, 7).transposed!(4, 0, 1).shape` returns `[7, 3, 4, 5, 6]`.) +$(T2 swapped, `1000.iota.sliced(3, 4, 5).swapped!(1, 2).shape` returns `[3, 5, 4]`.) +$(T2 everted, `1000.iota.sliced(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, `1000.iota.sliced(13, 40).strided!(0, 1)(2, 5).shape` equals to `[7, 8]`.) +$(T2 reversed, `slice.reversed!0` returns the slice with reversed direction of iteration for top level dimension.) +$(T2 allReversed, `20.iota.sliced(4, 5).allReversed` equals to `20.iota.retro.sliced(4, 5)`.) +) + +$(BOOKTABLE $(H2 Other operators), +$(TR $(TH Function Name) $(TH Description)) +$(T2 rotated, `10.iota.sliced(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: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: Ilya Yaroshenko + +Source: $(PHOBOSSRC std/_experimental/_ndslice/_iteration.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.iteration; + +import std.traits; + +import std.experimental.ndslice.internal; +import std.experimental.ndslice.slice; //: Slice; + +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) +{ + 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.range: iota; + assert(10000.iota + .sliced(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.range: iota; + assert(10000.iota + .sliced(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.range: iota; + assert(10000.iota + .sliced(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) +{ + 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.range: iota; + auto slice = 10.iota.sliced(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)(auto ref 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.range: iota; + assert(1000.iota + .sliced(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)(in 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) +{ + Slice!(N, Range) transposed(size_t N, Range)(auto ref 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)(auto ref Slice!(N, Range) slice, size_t dimension) +in +{ + mixin (DimensionRTError); +} +body +{ + size_t[1] permutation = void; + permutation[0] = dimension; + immutable perm = completeTranspose!N(permutation); + assert(perm.isPermutation, __PRETTY_FUNCTION__ ~ ": internal error."); + mixin (_transposedCode); +} + +///ditto +Slice!(N, Range) transposed(size_t N, Range)(auto ref Slice!(N, Range) slice, in size_t[] 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)(auto ref Slice!(2, Range) slice) +{ + return .transposed!(1, 0)(slice); +} + +/// Template +@safe @nogc pure nothrow unittest +{ + import std.experimental.ndslice.slice; + import std.range: iota; + assert(100000.iota + .sliced(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.range: iota; + assert(100000.iota + .sliced(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.range: iota; + assert(100000.iota + .sliced(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.range: iota; + assert(100.iota + .sliced(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) +{ + 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)(Slice!(N, Range) slice, size_t dimension) +in +{ + mixin (DimensionRTError); +} +body +{ + mixin (_reversedCode); + return slice; +} + +///ditto +Slice!(N, Range) reversed(size_t N, Range)(Slice!(N, Range) slice, in size_t[] dimensions...) +in +{ + foreach (dimension; dimensions) + mixin (DimensionRTError); +} +body +{ + foreach (dimension; dimensions) + mixin (_reversedCode); + return slice; +} + +/// +pure nothrow 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 = 100.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 the 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) +{ + auto strided(size_t N, Range)(Slice!(N, Range) slice, Repeat!(size_t, Dimensions.length) 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 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.range: iota; + static assert(1000.iota.sliced(13, 40).strided!(0, 1)(2, 5).shape == [7, 8]); + static assert(100.iota.sliced(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 = 100.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.range: iota, retro; + auto a = 20.iota.sliced(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.range: iota, retro; + auto a = 20.iota.sliced(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.range: iota, retro; + auto a = 20.iota.sliced(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) +{ + 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)(Slice!(N, Range) slice, size_t dimension) +in +{ + mixin (DimensionRTError); +} +body +{ + slice.popFront(dimension); + return slice; +} + +///ditto +Slice!(N, Range) dropOne(size_t N, Range)(Slice!(N, Range) slice, in size_t[] dimensions...) +in +{ + foreach (dimension; dimensions) + mixin (DimensionRTError); +} +body +{ + foreach (dimension; dimensions) + slice.popFront(dimension); + return slice; +} + +///ditto +template dropBackOne(Dimensions...) + if (Dimensions.length) +{ + 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)(Slice!(N, Range) slice, size_t dimension) +in +{ + mixin (DimensionRTError); +} +body +{ + slice.popBack(dimension); + return slice; +} + +///ditto +Slice!(N, Range) dropBackOne(size_t N, Range)(Slice!(N, Range) slice, in size_t[] dimensions...) +in +{ + foreach (dimension; dimensions) + mixin (DimensionRTError); +} +body +{ + foreach (dimension; dimensions) + slice.popBack(dimension); + return slice; +} + + +/// +@safe @nogc pure nothrow unittest +{ + import std.experimental.ndslice.slice; + import std.range: iota, retro; + auto a = 20.iota.sliced(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.range: iota, retro; + auto a = 20.iota.sliced(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) +{ + Slice!(N, Range) dropExactly(size_t N, Range)(Slice!(N, Range) slice, Repeat!(size_t, Dimensions.length) 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) +{ + Slice!(N, Range) dropBackExactly(size_t N, Range)(Slice!(N, Range) slice, Repeat!(size_t, Dimensions.length) 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.range: iota, retro; + auto a = 20.iota.sliced(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) +{ + Slice!(N, Range) drop(size_t N, Range)(Slice!(N, Range) slice, Repeat!(size_t, Dimensions.length) 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) +{ + Slice!(N, Range) dropBack(size_t N, Range)(Slice!(N, Range) slice, Repeat!(size_t, Dimensions.length) 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.range: iota, retro; + auto a = 20.iota.sliced(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.slice; + import std.range: iota, retro; + assert(1000.iota + .sliced(5, 3, 6, 7) + .dropToHypercube + .shape == cast(size_t[4])[3, 3, 3, 3]); +} diff --git a/std/experimental/ndslice/package.d b/std/experimental/ndslice/package.d new file mode 100644 index 00000000000..8d8ca7ff6af --- /dev/null +++ b/std/experimental/ndslice/package.d @@ -0,0 +1,562 @@ +/+ +## Guide for Slice/BLAS contributors + +1. Make sure functions are + a. inlined(!), + b. `@nogc`, + c. `nothrow`, + d. `pure`. + For this reason, it is preferable to use _simple_ `assert`s with messages + that can be computed at compile time. + The goals are: + 1. to reduce executable size for _any_ compilation mode + 2. to reduce template bloat in object files + 3. to reduce compilation time + 4. to allow users to write extern C bindings for code libraries on `Slice` type. + +2. `std.format`, `std.string`, and `std.conv` should not be used in error + message formatting.`"Use" ~ Concatenation.stringof`. + +3. `mixin template`s may be used for pretty error message formatting. + +4. `Exception`s/`enforce`s should no be used to check indexes and lengths. + Exceptions are only allowed for algorithms where validation of input data is + too complicated for the user. `reshape` function is a good example of a case + where Exceptions are required. + If a function might throw an exception, an example with exception handing should be added. + +5. For simple checks like matrix transposition, compile time flags should not be used. + It is much better to opt for runtime matrix transposition. + Furthermore, Slice type provides runtime matrix transposition out of the box. + +6. _Fortran_VS_C_ flags should not be used. They are about notation, + but not about the algorithm itself. For math world users, + a corresponding code example might be included in the documentation. + `transposed` / `everted` can be used in cache-friendly codes. + +7. Compile time evaluation should not be used to produce dummy types like `IdentityMatrix`. + +8. Memory allocation and algorithm logic should be separated whenever possible. + +9. CTFE unittests should be added to new functions. ++/ + +/** +$(H1 Multidimensional Random Access Ranges) + +The package provides a multidimensional array implementation, +It would be well suited to creating machine learning and image +processing algorithms, but should also be general enough for use anywhere with +homogeneously-typed multidimensional data. +In addition, it includes various functions for iteration, accessing, and manipulation. + +Quick_Start: +$(SUBREF slice, sliced) is a function designed to create +a multidimensional view over a range. +Multidimensional view is presented by $(SUBREF slice, Slice) type. +------ +auto matrix = new double[12].sliced(3, 4); +matrix[] = 0; +------ + +Note: +In many examples $(LINK2 std_range.html#iota, std.range.iota) is used +instead of a regular array, which makes it +possible to carry out tests without memory allocation. + +$(SCRIPT inhibitQuickIndex = 1;) + +$(DIVC quickindex, +$(BOOKTABLE , +$(TR $(TH Category) $(TH Submodule) $(TH Declarations) +) +$(TR $(TDNW Basic Level + $(BR) $(SMALL $(SUBREF slice, Slice), its properties, operator overloading)) + $(TDNW $(SUBMODULE slice)) + $(TD + $(SUBREF slice, sliced) + $(SUBREF slice, Slice) + $(SUBREF slice, assumeSameStructure) + $(SUBREF slice, ReplaceArrayWithPointer) + $(SUBREF slice, DeepElementType) + ) +) +$(TR $(TDNW Middle Level + $(BR) $(SMALL Various iteration operators)) + $(TDNW $(SUBMODULE iteration)) + $(TD + $(SUBREF iteration, transposed) + $(SUBREF iteration, strided) + $(SUBREF iteration, reversed) + $(SUBREF iteration, rotated) + $(SUBREF iteration, everted) + $(SUBREF iteration, swapped) + $(SUBREF iteration, allReversed) + $(SUBREF iteration, dropToHypercube) and other `drop` primitives + ) +) +$(TR $(TDNW Advanced Level $(BR) + $(SMALL Abstract operators for loop free programming + $(BR) Take `movingWindowByChannel` as an example)) + $(TDNW $(SUBMODULE selection)) + $(TD + $(SUBREF selection, blocks) + $(SUBREF selection, windows) + $(SUBREF selection, diagonal) + $(SUBREF selection, reshape) + $(SUBREF selection, byElement) + $(SUBREF selection, byElementInStandardSimplex) + $(SUBREF selection, indexSlice) + $(SUBREF selection, pack) + $(SUBREF selection, evertPack) + $(SUBREF selection, unpack) + ) +) +)) + +$(H2 Example: Image Processing) + +A median filter is implemented as an example. The function +`movingWindowByChannel` can also be used with other filters that use a sliding +window as the argument, in particular with convolution matrices such as the +$(LINK2 https://en.wikipedia.org/wiki/Sobel_operator, Sobel operator). + +`movingWindowByChannel` iterates over an image in sliding window mode. +Each window is transferred to a `filter`, which calculates the value of the +pixel that corresponds to the given window. + +This function does not calculate border cases in which a window overlaps +the image partially. However, the function can still be used to carry out such +calculations. That can be done by creating an amplified image, with the edges +reflected from the original image, and then applying the given function to the +new file. + +Note: You can find the example at +$(LINK2 https://github.com/DlangScience/examples/tree/master/image_processing/median-filter, GitHub). + +------- +/++ +Params: + filter = unary function. Dimension window 2D is the argument. + image = image dimensions `(h, w, c)`, + where с is the number of channels in the image + nr = number of rows in the window + nс = number of columns in the window + +Returns: + image dimensions `(h - nr + 1, w - nc + 1, c)`, + where с is the number of channels in the image. + Dense data layout is guaranteed. ++/ + +Slice!(3, C*) movingWindowByChannel(alias filter, C) +(Slice!(3, C*) image, size_t nr, size_t nc) +{ + import std.algorithm.iteration: map; + import std.array: array; + + // 0. 3D + // The last dimension represents the color channel. + auto wnds = image + // 1. 2D composed of 1D + // Packs the last dimension. + .pack!1 + // 2. 2D composed of 2D composed of 1D + // Splits image into overlapping windows. + .windows(nr, nc) + // 3. 5D + // Unpacks the windows. + .unpack + // 4. 5D + // Brings the color channel dimension to the third position. + .transposed!(0, 1, 4) + // 5. 3D Composed of 2D + // Packs the last two dimensions. + .pack!2; + + return wnds + // 6. Range composed of 2D + // Gathers all windows in the range. + .byElement + // 7. Range composed of pixels + // 2D to pixel lazy conversion. + .map!filter + // 8. `C[]` + // The only memory allocation in this function. + .array + // 9. 3D + // Returns slice with corresponding shape. + .sliced(wnds.shape); +} +------- + +A function that calculates the value of iterator median is also necessary. + +------- +/++ + +Params: + r = input range + buf = buffer with length no less than the number of elements in `r` +Returns: + median value over the range `r` ++/ +T median(Range, T)(Range r, T[] buf) +{ + import std.algorithm.sorting: sort; + size_t n; + foreach (e; r) + buf[n++] = e; + buf[0 .. n].sort(); + immutable m = n >> 1; + return n & 1 ? buf[m] : cast(T)((buf[m - 1] + buf[m]) / 2); +} +------- + +The `main` function: + +------- +void main(string[] args) +{ + import std.conv: to; + import std.getopt: getopt, defaultGetoptPrinter; + import std.path: stripExtension; + + uint nr, nc, def = 3; + auto helpInformation = args.getopt( + "nr", "number of rows in window, default value is " ~ def.to!string, &nr, + "nc", "number of columns in window, default value is equal to nr", &nc); + if (helpInformation.helpWanted) + { + defaultGetoptPrinter( + "Usage: median-filter [] []\noptions:", + helpInformation.options); + return; + } + if (!nr) nr = def; + if (!nc) nc = nr; + + auto buf = new ubyte[nr * nc]; + + foreach (name; args[1 .. $]) + { + import imageformats; // can be found at code.dlang.org + + IFImage image = read_image(name); + + auto ret = image.pixels + .sliced(cast(size_t)image.h, cast(size_t)image.w, cast(size_t)image.c) + .movingWindowByChannel + !(window => median(window.byElement, buf)) + (nr, nc); + + write_image( + name.stripExtension ~ "_filtered.png", + ret.length!1, + ret.length!0, + (&ret[0, 0, 0])[0 .. ret.elementsCount]); + } +} +------- + +This program works both with color and grayscale images. + +------- +$ median-filter --help +Usage: median-filter [] [] +options: + --nr number of rows in window, default value is 3 + --nc number of columns in window default value equals to nr +-h --help This help information. +------- + +$(H2 Compared with `numpy.ndarray`) + +numpy is undoubtedly one of the most effective software packages that has +facilitated the work of many engineers and scientists. However, due to the +specifics of implementation of Python, a programmer who wishes to use the +functions not represented in numpy may find that the built-in functions +implemented specifically for numpy are not enough, and their Python +implementations work at a very low speed. Extending numpy can be done, but +is somewhat laborious as even the most basic numpy functions that refer +directly to `ndarray` data must be implemented in C for reasonable performance. + +At the same time, while working with `ndslice`, an engineer has access to the +whole set of standard D library, so the functions he creates will be as +efficient as if they were written in C. + +License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: Ilya Yaroshenko + +Acknowledgements: John Loughran Colvin + +Source: $(PHOBOSSRC std/_experimental/_ndslice/_package.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; + +public import std.experimental.ndslice.slice; +public import std.experimental.ndslice.iteration; +public import std.experimental.ndslice.selection; + +// relaxed example +unittest +{ + static Slice!(3, ubyte*) movingWindowByChannel + (Slice!(3, ubyte*) image, size_t nr, size_t nc, ubyte delegate(Slice!(2, ubyte*)) filter) + { + import std.algorithm.iteration: map; + import std.array: array; + auto wnds = image + .pack!1 + .windows(nr, nc) + .unpack + .transposed!(0, 1, 4) + .pack!2; + return wnds + .byElement + .map!filter + .array + .sliced(wnds.shape); + } + + static T median(Range, T)(Range r, T[] buf) + { + import std.algorithm.sorting: sort; + size_t n; + foreach (e; r) + buf[n++] = e; + buf[0 .. n].sort(); + immutable m = n >> 1; + return n & 1 ? buf[m] : cast(T)((buf[m - 1] + buf[m]) / 2); + } + + import std.conv: to; + import std.getopt: getopt, defaultGetoptPrinter; + import std.path: stripExtension; + + auto args = ["std"]; + uint nr, nc, def = 3; + auto helpInformation = args.getopt( + "nr", "number of rows in window, default value is " ~ def.to!string, &nr, + "nc", "number of columns in window default value equals to nr", &nc); + if (helpInformation.helpWanted) + { + defaultGetoptPrinter( + "Usage: median-filter [] []\noptions:", + helpInformation.options); + return; + } + if (!nr) nr = def; + if (!nc) nc = nr; + + auto buf = new ubyte[nr * nc]; + + foreach (name; args[1 .. $]) + { + auto ret = + movingWindowByChannel + (new ubyte[300].sliced(10, 10, 3), nr, nc, window => median(window.byElement, buf)); + } +} + +@safe @nogc pure nothrow unittest +{ + import std.algorithm.comparison: equal; + import std.range: iota; + immutable r = 1_000_000.iota; + + auto t0 = r.sliced(1000); + assert(t0.front == 0); + assert(t0.back == 999); + assert(t0[9] == 9); + + auto t1 = t0[10 .. 20]; + assert(t1.front == 10); + assert(t1.back == 19); + assert(t1[9] == 19); + + t1.popFront(); + assert(t1.front == 11); + t1.popFront(); + assert(t1.front == 12); + + t1.popBack(); + assert(t1.back == 18); + t1.popBack(); + assert(t1.back == 17); + + assert(t1.equal(iota(12, 18))); +} + +pure nothrow unittest +{ + import std.algorithm.comparison: equal; + import std.array: array; + import std.range: iota; + auto r = 1_000.iota.array; + + auto t0 = r.sliced(1000); + assert(t0.length == 1000); + assert(t0.front == 0); + assert(t0.back == 999); + assert(t0[9] == 9); + + auto t1 = t0[10 .. 20]; + assert(t1.front == 10); + assert(t1.back == 19); + assert(t1[9] == 19); + + t1.popFront(); + assert(t1.front == 11); + t1.popFront(); + assert(t1.front == 12); + + t1.popBack(); + assert(t1.back == 18); + t1.popBack(); + assert(t1.back == 17); + + assert(t1.equal(iota(12, 18))); + + t1.front = 13; + assert(t1.front == 13); + t1.front++; + assert(t1.front == 14); + t1.front += 2; + assert(t1.front == 16); + t1.front = 12; + assert((t1.front = 12) == 12); + + t1.back = 13; + assert(t1.back == 13); + t1.back++; + assert(t1.back == 14); + t1.back += 2; + assert(t1.back == 16); + t1.back = 12; + assert((t1.back = 12) == 12); + + t1[3] = 13; + assert(t1[3] == 13); + t1[3]++; + assert(t1[3] == 14); + t1[3] += 2; + assert(t1[3] == 16); + t1[3] = 12; + assert((t1[3] = 12) == 12); + + t1[3 .. 5] = 100; + assert(t1[2] != 100); + assert(t1[3] == 100); + assert(t1[4] == 100); + assert(t1[5] != 100); + + t1[3 .. 5] += 100; + assert(t1[2] < 100); + assert(t1[3] == 200); + assert(t1[4] == 200); + assert(t1[5] < 100); + + --t1[3 .. 5]; + + assert(t1[2] < 100); + assert(t1[3] == 199); + assert(t1[4] == 199); + assert(t1[5] < 100); + + --t1[]; + assert(t1[3] == 198); + assert(t1[4] == 198); + + t1[] += 2; + assert(t1[3] == 200); + assert(t1[4] == 200); + + t1[] *= t1[]; + assert(t1[3] == 40000); + assert(t1[4] == 40000); + + + assert(&t1[$ - 1] is &(t1.back())); +} + +@safe @nogc pure nothrow unittest +{ + import std.range: iota; + auto r = (10_000L * 2 * 3 * 4).iota; + + auto t0 = r.sliced(10, 20, 30, 40); + assert(t0.length == 10); + assert(t0.length!0 == 10); + assert(t0.length!1 == 20); + assert(t0.length!2 == 30); + assert(t0.length!3 == 40); +} + +pure nothrow unittest +{ + import std.experimental.ndslice.internal: Iota; + import std.meta: AliasSeq; + import std.range; + import std.typecons: Tuple; + foreach (R; AliasSeq!( + int*, int[], typeof(1.iota), + const(int)*, const(int)[], + immutable(int)*, immutable(int)[], + double*, double[], typeof(10.0.iota), + Tuple!(double, int[string])*, Tuple!(double, int[string])[])) + foreach (n; Iota!(1, 4)) + { + alias S = Slice!(n, R); + static assert(isRandomAccessRange!S); + static assert(hasSlicing!S); + static assert(hasLength!S); + } + + immutable int[] im = [1,2,3,4,5,6]; + auto slice = im.sliced(2, 3); +} + +pure nothrow unittest +{ + auto tensor = new int[100].sliced(3, 4, 8); + assert(&(tensor.back.back.back()) is &tensor[2, 3, 7]); + assert(&(tensor.front.front.front()) is &tensor[0, 0, 0]); +} + +pure nothrow unittest +{ + import std.experimental.ndslice.selection: pack; + auto slice = new int[24].sliced(2, 3, 4); + auto r0 = slice.pack!1[1, 2]; + slice.pack!1[1, 2][] = 4; + auto r1 = slice[1, 2]; + assert(slice[1, 2, 3] == 4); +} + +pure nothrow unittest +{ + auto ar = new int[3 * 8 * 9]; + + auto tensor = ar.sliced(3, 8, 9); + tensor[0, 1, 2] = 4; + tensor[0, 1, 2]++; + assert(tensor[0, 1, 2] == 5); + tensor[0, 1, 2]--; + assert(tensor[0, 1, 2] == 4); + tensor[0, 1, 2] += 2; + assert(tensor[0, 1, 2] == 6); + + auto matrix = tensor[0 .. $, 1, 0 .. $]; + matrix[] = 10; + assert(tensor[0, 1, 2] == 10); + assert(matrix[0, 2] == tensor[0, 1, 2]); + assert(&matrix[0, 2] is &tensor[0, 1, 2]); +} diff --git a/std/experimental/ndslice/selection.d b/std/experimental/ndslice/selection.d new file mode 100644 index 00000000000..5940c695d07 --- /dev/null +++ b/std/experimental/ndslice/selection.d @@ -0,0 +1,1524 @@ +/** +$(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); +} diff --git a/std/experimental/ndslice/slice.d b/std/experimental/ndslice/slice.d new file mode 100644 index 00000000000..ce9ddb95a82 --- /dev/null +++ b/std/experimental/ndslice/slice.d @@ -0,0 +1,2654 @@ +/** +This is a submodule of $(LINK2 std_experimental_ndslice.html, std.experimental.ndslice). + +License: $(WEB www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: Ilya Yaroshenko + +Source: $(PHOBOSSRC std/_experimental/_ndslice/_slice.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)) +STD = $(TD $(SMALL $0)) +*/ +module std.experimental.ndslice.slice; + +import std.traits; +import std.meta; +import std.typecons; //: Flag; + +import std.experimental.ndslice.internal; + +/++ +Creates an n-dimensional slice-shell over a `range`. +Params: + range = a random access range or an array; only index operator + `auto opIndex(size_t index)` is required for ranges. The length of the + range must be greater than or equal to the sum of shift and the product of + lengths. + lengths = list of lengths for each dimension + shift = index of the first element of a `range`. + The first `shift` elements of range are ignored. + Names = names of elements in a slice tuple. + Slice tuple is a slice, which holds single set of lengths and strides + for a number of ranges. +Returns: + n-dimensional slice ++/ +auto sliced(ReplaceArrayWithPointer mod = ReplaceArrayWithPointer.yes, Range, Lengths...)(Range range, Lengths lengths) + if (!isStaticArray!Range && !isNarrowString!Range + && allSatisfy!(isIndex, Lengths) && Lengths.length) +{ + return .sliced!(mod, Lengths.length, Range)(range, [lengths]); +} + +///ditto +auto sliced(ReplaceArrayWithPointer mod = ReplaceArrayWithPointer.yes, size_t N, Range)(Range range, auto ref in size_t[N] lengths, size_t shift = 0) + if (!isStaticArray!Range && !isNarrowString!Range && N) +in +{ + import std.range.primitives: hasLength; + foreach (len; lengths) + assert(len > 0, + "All lengths must be positive." + ~ tailErrorMessage!()); + static if (hasLength!Range) + assert(lengthsProduct!N(lengths) + shift <= range.length, + "Range length must be greater than or equal to the sum of shift and the product of lengths." + ~ tailErrorMessage!()); +} +body +{ + static if (isDynamicArray!Range && mod) + { + Slice!(N, typeof(range.ptr)) ret = void; + ret._ptr = range.ptr + shift; + } + else + { + alias S = Slice!(N, ImplicitlyUnqual!(typeof(range))); + static if (hasElaborateAssign!(S.PureRange)) + S ret; + else + S ret = void; + static if (hasPtrBehavior!(S.PureRange)) + { + static if (S.NSeq.length == 1) + ret._ptr = range; + else + ret._ptr = range._ptr; + ret._ptr += shift; + } + else + { + static if (S.NSeq.length == 1) + { + ret._ptr._range = range; + ret._ptr._shift = shift; + } + else + { + ret._ptr = range._ptr; + ret._ptr._shift += range._strides[0] * shift; + } + } + } + ret._lengths[N - 1] = lengths[N - 1]; + static if (ret.NSeq.length == 1) + ret._strides[N - 1] = 1; + else + ret._strides[N - 1] = range._strides[0]; + foreach_reverse(i; Iota!(0, N - 1)) + { + ret._lengths[i] = lengths[i]; + ret._strides[i] = ret._strides[i + 1] * ret._lengths[i + 1]; + } + foreach (i; Iota!(N, ret.PureN)) + { + ret._lengths[i] = range._lengths[i - N + 1]; + ret._strides[i] = range._strides[i - N + 1]; + } + return ret; +} + +private enum bool _isSlice(T) = is(T : Slice!(N, Range), size_t N, Range); + +///ditto +template sliced(Names...) + if (Names.length && !anySatisfy!(isType, Names) && allSatisfy!(isStringValue, Names)) +{ + mixin ( + " + auto sliced( + ReplaceArrayWithPointer mod = ReplaceArrayWithPointer.yes, + " ~ _Range_Types!Names ~ " + Lengths...) + (" ~ _Range_DeclarationList!Names ~ + "Lengths lengths) + if (allSatisfy!(isIndex, Lengths)) + { + return .sliced!Names(" ~ _Range_Values!Names ~ "[lengths]); + } + + auto sliced( + ReplaceArrayWithPointer mod = ReplaceArrayWithPointer.yes, + size_t N, " ~ _Range_Types!Names ~ ") + (" ~ _Range_DeclarationList!Names ~" + auto ref in size_t[N] lengths, + size_t shift = 0) + { + alias RS = AliasSeq!(" ~ _Range_Types!Names ~ ");" + ~ q{ + import std.range.primitives: hasLength; + import std.meta: staticMap; + static assert(!anySatisfy!(_isSlice, RS), + `Packed slices are not allowed in slice tuples` + ~ tailErrorMessage!()); + alias PT = PtrTuple!Names; + alias SPT = PT!(staticMap!(PrepareRangeType, RS)); + static if (hasElaborateAssign!SPT) + SPT range; + else + SPT range = void; + version(assert) immutable minLength = lengthsProduct!N(lengths) + shift; + foreach (i, name; Names) + { + alias T = typeof(range.ptrs[i]); + alias R = RS[i]; + static assert(!isStaticArray!R); + static assert(!isNarrowString!R); + mixin (`alias r = range_` ~ name ~`;`); + static if (hasLength!R) + assert(minLength <= r.length, + `length of range '` ~ name ~`' must be greater than or equal ` + ~ `to the sum of shift and the product of lengths.` + ~ tailErrorMessage!()); + static if (isDynamicArray!T && mod) + range.ptrs[i] = r.ptr; + else + range.ptrs[i] = T(0, r); + } + return .sliced!(mod, N, SPT)(range, lengths, shift); + } + ~ "}"); +} + +/// Creates a slice from an array. +pure nothrow unittest +{ + auto slice = new int [1000].sliced(5, 6, 7); + assert(slice.length == 5); + assert(slice.elementsCount == 5 * 6 * 7); + static assert(is(typeof(slice) == Slice!(3, int*))); +} + +/// Creates a slice using shift parameter. +@safe @nogc pure nothrow unittest +{ + import std.range: iota; + auto slice = 1000.iota.sliced([5, 6, 7], 9); + assert(slice.length == 5); + assert(slice.elementsCount == 5 * 6 * 7); + assert(slice[0, 0, 0] == 9); +} + +/// $(LINK2 https://en.wikipedia.org/wiki/Vandermonde_matrix, Vandermonde matrix) +pure nothrow unittest +{ + pure nothrow + Slice!(2, double*) vandermondeMatrix(Slice!(1, double*) x) + { + auto ret = new double[x.length ^^ 2] + .sliced(x.length, x.length); + foreach (i; 0 .. x.length) + foreach (j; 0 .. x.length) + ret[i, j] = x[i] ^^ j; + return ret; + } + + auto x = [1.0, 2, 3, 4, 5].sliced(5); + auto v = vandermondeMatrix(x); + assert(v == + [[ 1.0, 1, 1, 1, 1], + [ 1.0, 2, 4, 8, 16], + [ 1.0, 3, 9, 27, 81], + [ 1.0, 4, 16, 64, 256], + [ 1.0, 5, 25, 125, 625]]); +} + +/++ +Creates a slice composed of named elements, each one of which corresponds +to a given argument. See also $(LREF assumeSameStructure). ++/ +pure nothrow unittest +{ + import std.algorithm.comparison: equal; + import std.experimental.ndslice.selection: byElement; + import std.range: iota; + + auto alpha = 12.iota; + auto beta = new int[12]; + + auto m = sliced!("a", "b")(alpha, beta, 4, 3); + foreach (r; m) + foreach (e; r) + e.b = e.a; + assert(equal(alpha, beta)); + + beta[] = 0; + foreach (e; m.byElement) + e.b = e.a; + assert(equal(alpha, beta)); +} + +/++ +Creates an array and an n-dimensional slice over it. ++/ +pure nothrow unittest +{ + auto createSlice(T, Lengths...)(Lengths lengths) + { + return createSlice2!(T, Lengths.length)(cast(size_t[Lengths.length])[lengths]); + } + + ///ditto + auto createSlice2(T, size_t N)(auto ref size_t[N] lengths) + { + size_t length = lengths[0]; + foreach (len; lengths[1 .. N]) + length *= len; + return new T[length].sliced(lengths); + } + + auto slice = createSlice!int(5, 6, 7); + assert(slice.length == 5); + assert(slice.elementsCount == 5 * 6 * 7); + static assert(is(typeof(slice) == Slice!(3, int*))); + + auto duplicate = createSlice2!int(slice.shape); + duplicate[] = slice; +} + +/++ +Creates a common n-dimensional array. ++/ +pure nothrow unittest +{ + auto ndarray(size_t N, Range)(auto ref Slice!(N, Range) slice) + { + import std.array: array; + static if (N == 1) + { + return slice.array; + } + else + { + import std.algorithm.iteration: map; + return slice.map!(a => ndarray(a)).array; + } + } + + import std.range: iota; + auto ar = ndarray(100.iota.sliced(3, 4)); + static assert(is(typeof(ar) == int[][])); + assert(ar == [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]); +} + +/++ +Allocates an array through a specified allocator and creates an n-dimensional slice over it. +See also $(LINK2 std_experimental_allocator.html, std.experimental.allocator). ++/ +version(Posix) +unittest +{ + import std.experimental.allocator; + + + // `theAllocator.makeSlice(3, 4)` allocates an array with length equal to `12` + // and returns this array and a `2`-dimensional slice-shell over it. + auto makeSlice(T, Allocator, Lengths...)(auto ref Allocator alloc, Lengths lengths) + { + enum N = Lengths.length; + struct Result { T[] array; Slice!(N, T*) slice; } + size_t length = lengths[0]; + foreach (len; lengths[1 .. N]) + length *= len; + T[] a = alloc.makeArray!T(length); + return Result(a, a.sliced(lengths)); + } + + auto tup = makeSlice!int(theAllocator, 2, 3, 4); + + static assert(is(typeof(tup.array) == int[])); + static assert(is(typeof(tup.slice) == Slice!(3, int*))); + + assert(tup.array.length == 24); + assert(tup.slice.elementsCount == 24); + assert(tup.array.ptr == &tup.slice[0, 0, 0]); + + theAllocator.dispose(tup.array); +} + +/// Input range primitives for slices over user defined types +pure nothrow @nogc unittest +{ + struct MyIota + { + //`[index]` operator overloading + auto opIndex(size_t index) + { + return index; + } + } + + alias S = Slice!(3, MyIota); + auto slice = MyIota().sliced(20, 10); + + import std.range.primitives; + static assert(hasLength!S); + static assert(isInputRange!S); + static assert(isForwardRange!S == false); +} + +/// Random access range primitives for slices over user defined types +pure nothrow @nogc unittest +{ + struct MyIota + { + //`[index]` operator overloading + auto opIndex(size_t index) + { + return index; + } + // `save` property to allow a slice to be a forward range + auto save() @property + { + return this; + } + } + + alias S = Slice!(3, MyIota); + auto slice = MyIota().sliced(20, 10); + + import std.range.primitives; + static assert(hasLength!S); + static assert(hasSlicing!S); + static assert(isForwardRange!S); + static assert(isBidirectionalRange!S); + static assert(isRandomAccessRange!S); +} + +// sliced slice +pure nothrow unittest +{ + import std.range: iota; + auto data = new int[24]; + foreach (int i,ref e; data) + e = i; + auto a = data.sliced(10).sliced(2, 3); + auto b = 24.iota.sliced(10).sliced(2, 3); + assert(a == b); + a[] += b; + foreach (int i, e; data[0..6]) + assert(e == 2*i); + foreach (int i, e; data[6..$]) + assert(e == i+6); + auto c = data.sliced(12, 2).sliced(2, 3); + auto d = 24.iota.sliced(12, 2).sliced(2, 3); + auto cc = data.sliced(2, 3, 2); + auto dc = 24.iota.sliced(2, 3, 2); + assert(c._lengths == cc._lengths); + assert(c._strides == cc._strides); + assert(d._lengths == dc._lengths); + assert(d._strides == dc._strides); + assert(cc == c); + assert(dc == d); + auto e = data.sliced(8, 3).sliced(5); + auto f = 24.iota.sliced(8, 3).sliced(5); + assert(e == data.sliced(5, 3)); + assert(f == 24.iota.sliced(5, 3)); +} + +private template _Range_Types(Names...) +{ + static if (Names.length) + enum string _Range_Types = "Range_" ~ Names[0] ~ ", " ~ _Range_Types!(Names[1..$]); + else + enum string _Range_Types = ""; +} + +private template _Range_Values(Names...) +{ + static if (Names.length) + enum string _Range_Values = "range_" ~ Names[0] ~ ", " ~ _Range_Values!(Names[1..$]); + else + enum string _Range_Values = ""; +} + +private template _Range_DeclarationList(Names...) +{ + static if (Names.length) + enum string _Range_DeclarationList = "Range_" ~ Names[0] ~ " range_" ~ Names[0] ~ ", " ~ _Range_DeclarationList!(Names[1..$]); + else + enum string _Range_DeclarationList = ""; +} + +private template _Slice_DeclarationList(Names...) +{ + static if (Names.length) + enum string _Slice_DeclarationList = "Slice!(N, Range_" ~ Names[0] ~ ") slice_" ~ Names[0] ~ ", " ~ _Slice_DeclarationList!(Names[1..$]); + else + enum string _Slice_DeclarationList = ""; +} + +/++ +Groups slices into a slice tuple. The slices must have identical structure. +Slice tuple is a slice, which holds single set of lengths and strides +for a number of ranges. +Params: + Names = names of elements in a slice tuple +Returns: + n-dimensional slice +See_also: $(LREF .Slice.structure). ++/ +template assumeSameStructure(Names...) + if (Names.length && !anySatisfy!(isType, Names) && allSatisfy!(isStringValue, Names)) +{ + mixin ( + " + auto assumeSameStructure( + ReplaceArrayWithPointer mod = ReplaceArrayWithPointer.yes, + size_t N, " ~ _Range_Types!Names ~ ") + (" ~ _Slice_DeclarationList!Names ~ ") + { + alias RS = AliasSeq!(" ~_Range_Types!Names ~ ");" + ~ q{ + import std.meta: staticMap; + static assert(!anySatisfy!(_isSlice, RS), + `Packed slices not allowed in slice tuples` + ~ tailErrorMessage!()); + alias PT = PtrTuple!Names; + alias SPT = PT!(staticMap!(PrepareRangeType, RS)); + static if (hasElaborateAssign!SPT) + Slice!(N, SPT) ret; + else + Slice!(N, SPT) ret = void; + mixin (`alias slice0 = slice_` ~ Names[0] ~`;`); + ret._lengths = slice0._lengths; + ret._strides = slice0._strides; + ret._ptr.ptrs[0] = slice0._ptr; + foreach (i, name; Names[1..$]) + { + mixin (`alias slice = slice_` ~ name ~`;`); + assert(ret._lengths == slice._lengths, + `Shapes must be identical` + ~ tailErrorMessage!()); + assert(ret._strides == slice._strides, + `Strides must be identical` + ~ tailErrorMessage!()); + ret._ptr.ptrs[i+1] = slice._ptr; + } + return ret; + } + ~ "}"); +} + +/// +pure nothrow unittest +{ + import std.algorithm.comparison: equal; + import std.experimental.ndslice.selection: byElement; + import std.range: iota; + + auto alpha = 12.iota .sliced(4, 3); + auto beta = new int[12].sliced(4, 3); + + auto m = assumeSameStructure!("a", "b")(alpha, beta); + foreach (r; m) + foreach (e; r) + e.b = e.a; + assert(alpha == beta); + + beta[] = 0; + foreach (e; m.byElement) + e.b = e.a; + assert(alpha == beta); +} + +/++ +If `yes`, the array will be replaced with its pointer to improve performance. +Use `no` for compile time function evaluation. ++/ +alias ReplaceArrayWithPointer = Flag!"replaceArrayWithPointer"; + +/// +@safe pure nothrow unittest +{ + import std.algorithm.iteration: map, sum, reduce; + import std.algorithm.comparison: max; + import std.experimental.ndslice.iteration: transposed; + /// Returns maximal column average. + auto maxAvg(S)(S matrix) { + return matrix.transposed.map!sum.reduce!max + / matrix.length; + } + enum matrix = [1, 2, + 3, 4].sliced!(ReplaceArrayWithPointer.no)(2, 2); + ///Сompile time function evaluation + static assert(maxAvg(matrix) == 3); +} + + +/++ +Returns the element type of the `Slice` type. ++/ +alias DeepElementType(S : Slice!(N, Range), size_t N, Range) = S.DeepElemType; + +/// +unittest +{ + import std.range: iota; + static assert(is(DeepElementType!(Slice!(4, const(int)[])) == const(int))); + static assert(is(DeepElementType!(Slice!(4, immutable(int)*)) == immutable(int))); + static assert(is(DeepElementType!(Slice!(4, typeof(100.iota))) == int)); + //packed slice + static assert(is(DeepElementType!(Slice!(2, Slice!(5, int*))) == Slice!(4, int*))); +} + +/++ +Presents $(LREF .Slice.structure). ++/ +struct Structure(size_t N) +{ + /// + size_t[N] lengths; + /// + sizediff_t[N] strides; +} + +/++ +Presents an n-dimensional view over a range. + +$(H3 Definitions) + +In order to change data in a slice using +overloaded operators such as `=`, `+=`, `++`, +a syntactic structure of type +`[]` must be used. +It is worth noting that just like for regular arrays, operations `a = b` +and `a[] = b` have different meanings. +In the first case, after the operation is carried out, `a` simply points at the same data as `b` +does, and the data which `a` previously pointed at remains unmodified. +Here, `а` and `b` must be of the same type. +In the second case, `a` points at the same data as before, +but the data itself will be changed. In this instance, the number of dimensions of `b` +may be less than the number of dimensions of `а`; and `b` can be a Slice, +a regular multidimensional array, or simply a value (e.g. a number). + +In the following table you will find the definitions you might come across +in comments on operator overloading. + +$(BOOKTABLE +$(TR $(TH Definition) $(TH Examples at `N == 3`)) +$(TR $(TD An $(BLUE interval) is a part of a sequence of type `i .. j`.) + $(STD `2..$-3`, `0..4`)) +$(TR $(TD An $(BLUE index) is a part of a sequence of type `i`.) + $(STD `3`, `$-1`)) +$(TR $(TD A $(BLUE partially defined slice) is a sequence composed of + $(BLUE intervals) and $(BLUE indexes) with an overall length strictly less than `N`.) + $(STD `[3]`, `[0..$]`, `[3, 3]`, `[0..$,0..3]`, `[0..$,2]`)) +$(TR $(TD A $(BLUE fully defined index) is a sequence + composed only of $(BLUE indexes) with an overall length equal to `N`.) + $(STD `[2,3,1]`)) +$(TR $(TD A $(BLUE fully defined slice) is an empty sequence + or a sequence composed of $(BLUE indexes) and at least one + $(BLUE interval) with an overall length equal to `N`.) + $(STD `[]`, `[3..$,0..3,0..$-1]`, `[2,0..$,1]`)) +) + +$(H3 Internal Binary Representation) + +Multidimensional $(SUBREF slice, Slice) is a structure that consists of lengths, strides, and a pointer. +For ranges, a shell is used instead of a pointer. +This shell contains a shift of the current initial element of a multidimensional slice +and the range itself. With the exception of overloaded operators, no functions in this +package change or copy data. The operations are only carried out on lengths, strides, +and pointers. If a slice is defined over a range, only the shift of the initial element +changes instead of the pointer. + +$(H4 Internal Representation for Pointers) + +Type definition + +------- +Slice!(N, T*) +------- + +Schema + +------- +Slice!(N, T*) + size_t[N] lengths + sizediff_t[N] strides + T* ptr +------- + +Example: + +Definitions + +------- +import std.experimental.ndslice; +auto a = new double[24]; +Slice!(3, double*) s = a.sliced(2, 3, 4); +Slice!(3, double*) t = s.transposed!(1, 2, 0); +Slice!(3, double*) r = r.reversed!1; +------- + +Representation + +------- +s________________________ + lengths[0] ::= 2 + lengths[1] ::= 3 + lengths[2] ::= 4 + + strides[0] ::= 12 + strides[1] ::= 4 + strides[2] ::= 1 + + ptr ::= &a[0] + +t____transposed!(1, 2, 0) + lengths[0] ::= 3 + lengths[1] ::= 4 + lengths[2] ::= 2 + + strides[0] ::= 4 + strides[1] ::= 1 + strides[2] ::= 12 + + ptr ::= &a[0] + +r______________reversed!1 + lengths[0] ::= 2 + lengths[1] ::= 3 + lengths[2] ::= 4 + + strides[0] ::= 12 + strides[1] ::= -4 + strides[2] ::= 1 + + ptr ::= &a[8] // (old_strides[1] * (lengths[1] - 1)) = 8 +------- + +$(H4 Internal Representation for Ranges) + +Type definition + +------- +Slice!(N, Range) +------- + +Representation + +------- +Slice!(N, Range) + size_t[N] lengths + sizediff_t[N] strides + PtrShell!T ptr + sizediff_t shift + Range range +------- + + +Example: + +Definitions + +------- +import std.experimental.ndslice; +import std.range: iota; +auto a = iota(24); +alias A = typeof(a); +Slice!(3, A) s = a.sliced(2, 3, 4); +Slice!(3, A) t = s.transposed!(1, 2, 0); +Slice!(3, A) r = r.reversed!1; +------- + +Representation + +------- +s________________________ + lengths[0] ::= 2 + lengths[1] ::= 3 + lengths[2] ::= 4 + + strides[0] ::= 12 + strides[1] ::= 4 + strides[2] ::= 1 + + shift ::= 0 + range ::= a + +t____transposed!(1, 2, 0) + lengths[0] ::= 3 + lengths[1] ::= 4 + lengths[2] ::= 2 + + strides[0] ::= 4 + strides[1] ::= 1 + strides[2] ::= 12 + + shift ::= 0 + range ::= a + +r______________reversed!1 + lengths[0] ::= 2 + lengths[1] ::= 3 + lengths[2] ::= 4 + + strides[0] ::= 12 + strides[1] ::= -4 + strides[2] ::= 1 + + shift ::= 8 // (old_strides[1] * (lengths[1] - 1)) = 8 + range ::= a +------- ++/ +struct Slice(size_t _N, _Range) + if (_N && _N < 256LU && ((!is(Unqual!_Range : Slice!(N0, Range0), size_t N0, Range0) + && (isPointer!_Range || is(typeof(_Range.init[size_t.init])))) + || is(_Range == Slice!(N1, Range1), size_t N1, Range1))) +{ + package: + + enum doUnittest = is(_Range == int*) && _N == 1; + + alias N = _N; + alias Range = _Range; + + alias This = Slice!(N, Range); + static if (is(Range == Slice!(N_, Range_), size_t N_, Range_)) + { + enum size_t PureN = N + Range.PureN - 1; + alias PureRange = Range.PureRange; + alias NSeq = AliasSeq!(N, Range.NSeq); + } + else + { + alias PureN = N; + alias PureRange = Range; + alias NSeq = AliasSeq!(N); + } + alias PureThis = Slice!(PureN, PureRange); + + static assert(PureN < 256, "Slice: Pure N should be less than 256"); + + static if (N == 1) + alias ElemType = typeof(Range.init[size_t.init]); + else + alias ElemType = Slice!(N-1, Range); + + static if (NSeq.length == 1) + alias DeepElemType = typeof(Range.init[size_t.init]); + else + static if (Range.N == 1) + alias DeepElemType = Range.ElemType; + else + alias DeepElemType = Slice!(Range.N - 1, Range.Range); + + enum hasAccessByRef = isPointer!PureRange || + __traits(compiles, { auto a = &(_ptr[0]); } ); + + enum PureIndexLength(Slices...) = Filter!(isIndex, Slices).length; + template isFullPureIndex(Indexes...) + { + static if (allSatisfy!(isIndex, Indexes)) + enum isFullPureIndex = Indexes.length == N; + else + static if (Indexes.length == 1 && isStaticArray!(Indexes[0])) + enum isFullPureIndex = Indexes[0].length == N && isIndex!(ForeachType!(Indexes[0])); + else + enum isFullPureIndex = false; + } + enum isPureSlice(Slices...) = + Slices.length <= N + && PureIndexLength!Slices < N + && Filter!(isStaticArray, Slices).length == 0; + + enum isFullPureSlice(Slices...) = + Slices.length == 0 + || Slices.length == N + && PureIndexLength!Slices < N + && Filter!(isStaticArray, Slices).length == 0; + + size_t[PureN] _lengths; + sizediff_t[PureN] _strides; + static if (hasPtrBehavior!PureRange) + PureRange _ptr; + else + PtrShell!PureRange _ptr; + + sizediff_t backIndex(size_t dimension = 0)() @property const + if (dimension < N) + { + return _strides[dimension] * (_lengths[dimension] - 1); + } + + size_t indexStride(Indexes...)(Indexes _indexes) + if (isFullPureIndex!Indexes) + { + static if (isStaticArray!(Indexes[0])) + { + size_t stride; + foreach (i; Iota!(0, N)) //static + { + assert(_indexes[0][i] < _lengths[i], "indexStride: index must be less than lengths"); + stride += _strides[i] * _indexes[0][i]; + } + return stride; + } + else + { + size_t stride; + foreach (i, index; _indexes) //static + { + assert(index < _lengths[i], "indexStride: index must be less than lengths"); + stride += _strides[i] * index; + } + return stride; + } + } + + this(ref in size_t[PureN] lengths, ref in sizediff_t[PureN] strides, PureRange range) + { + foreach (i; Iota!(0, PureN)) + _lengths[i] = lengths[i]; + foreach (i; Iota!(0, PureN)) + _strides[i] = strides[i]; + static if (hasPtrBehavior!PureRange) + _ptr = range; + else + _ptr._range = range; + + } + + static if (!hasPtrBehavior!PureRange) + this(ref in size_t[PureN] lengths, ref in sizediff_t[PureN] strides, PtrShell!PureRange shell) + { + foreach (i; Iota!(0, PureN)) + _lengths[i] = lengths[i]; + foreach (i; Iota!(0, PureN)) + _strides[i] = strides[i]; + _ptr = shell; + } + + public: + + /++ + Returns: static array of lengths + See_also: $(LREF .Slice.structure) + +/ + size_t[N] shape() @property const + { + pragma(inline, true); + return _lengths[0 .. N]; + } + + static if (doUnittest) + /// Regular slice + @safe @nogc pure nothrow unittest + { + import std.range: iota; + assert(100.iota + .sliced(3, 4, 5) + .shape == cast(size_t[3])[3, 4, 5]); + } + + static if (doUnittest) + /// Packed slice + @safe @nogc pure nothrow unittest + { + import std.experimental.ndslice.selection: pack; + import std.range: iota; + assert(10000.iota + .sliced(3, 4, 5, 6, 7) + .pack!2 + .shape == cast(size_t[3])[3, 4, 5]); + } + + /++ + Returns: static array of lengths and static array of strides + See_also: $(LREF .Slice.shape) + +/ + Structure!N structure() @property const + { + pragma(inline, true); + return typeof(return)(_lengths[0 .. N], _strides[0 .. N]); + } + + static if (doUnittest) + /// Regular slice + @safe @nogc pure nothrow unittest + { + import std.range: iota; + assert(100.iota + .sliced(3, 4, 5) + .structure == Structure!3([3, 4, 5], [20, 5, 1])); + } + + static if (doUnittest) + /// Modified regular slice + @safe @nogc pure nothrow unittest + { + import std.experimental.ndslice.selection: pack; + import std.experimental.ndslice.iteration: reversed, strided, transposed; + import std.range: iota; + assert(1000.iota + .sliced(3, 4, 50) + .reversed!2 //makes stride negative + .strided!2(6) //multiplies stride by 6 and changes corresponding length + .transposed!2 //brings dimension `2` to the first position + .structure == Structure!3([9, 3, 4], [-6, 200, 50])); + } + + static if (doUnittest) + /// Packed slice + @safe @nogc pure nothrow unittest + { + import std.experimental.ndslice.selection: pack; + import std.range: iota; + assert(10000.iota + .sliced(3, 4, 5, 6, 7) + .pack!2 + .structure == Structure!3([3, 4, 5], [20 * 42, 5 * 42, 1 * 42])); + } + + /++ + Range primitive. + Defined only if `Range` is a forward range or a pointer type. + +/ + static if (canSave!PureRange) + auto save() @property + { + static if (isPointer!PureRange) + return typeof(this)(_lengths, _strides, _ptr); + else + return typeof(this)(_lengths, _strides, _ptr.save); + } + + static if (doUnittest) + /// Forward range + @safe @nogc pure nothrow unittest + { + import std.range: iota; + auto slice = 100.iota.sliced(2, 3).save; + } + + static if (doUnittest) + /// Pointer type. + pure nothrow unittest + { + //slice type is `Slice!(2, int*)` + auto slice = new int[6].sliced(2, 3).save; + } + + + /++ + Multidimensional `length` property. + Returns: length of the corresponding dimension + See_also: $(LREF .Slice.shape), $(LREF .Slice.structure) + +/ + size_t length(size_t dimension = 0)() @property const + if (dimension < N) + { + pragma(inline, true); + return _lengths[dimension]; + } + + static if (doUnittest) + /// + @safe @nogc pure nothrow unittest + { + import std.range: iota; + auto slice = 100.iota.sliced(3, 4, 5); + assert(slice.length == 3); + assert(slice.length!0 == 3); + assert(slice.length!1 == 4); + assert(slice.length!2 == 5); + } + + alias opDollar = length; + + /++ + Multidimensional `stride` property. + Returns: stride of the corresponding dimension + See_also: $(LREF .Slice.structure) + +/ + size_t stride(size_t dimension = 0)() @property const + if (dimension < N) + { + return _strides[dimension]; + } + + static if (doUnittest) + /// Regular slice + @safe @nogc pure nothrow unittest + { + import std.range: iota; + auto slice = 100.iota.sliced(3, 4, 5); + assert(slice.stride == 20); + assert(slice.stride!0 == 20); + assert(slice.stride!1 == 5); + assert(slice.stride!2 == 1); + } + + static if (doUnittest) + /// Modified regular slice + @safe @nogc pure nothrow unittest + { + import std.experimental.ndslice.iteration: reversed, strided, swapped; + import std.range: iota; + assert(1000.iota + .sliced(3, 4, 50) + .reversed!2 //makes stride negative + .strided!2(6) //multiplies stride by 6 and changes the corresponding length + .swapped!(1, 2) //swaps dimensions `1` and `2` + .stride!1 == -6); + } + + /++ + Multidimensional input range primitive. + +/ + bool empty(size_t dimension = 0)() + @property const + if (dimension < N) + { + pragma(inline, true); + return _lengths[dimension] == 0; + } + + ///ditto + auto ref front(size_t dimension = 0)() @property + if (dimension < N) + { + assert(!empty!dimension); + static if (PureN == 1) + { + static if (__traits(compiles,{ auto _f = _ptr.front; })) + return _ptr.front; + else + return _ptr[0]; + } + else + { + static if (hasElaborateAssign!PureRange) + ElemType ret; + else + ElemType ret = void; + foreach (i; Iota!(0, dimension)) + { + ret._lengths[i] = _lengths[i]; + ret._strides[i] = _strides[i]; + } + foreach (i; Iota!(dimension, PureN-1)) + { + ret._lengths[i] = _lengths[i + 1]; + ret._strides[i] = _strides[i + 1]; + } + ret._ptr = _ptr; + return ret; + } + } + + static if (PureN == 1 && isMutable!DeepElemType && !hasAccessByRef) + { + ///ditto + auto front(size_t dimension = 0, T)(T value) @property + if (dimension == 0) + { + assert(!empty!dimension); + static if (__traits(compiles, { _ptr.front = value; })) + return _ptr.front = value; + else + return _ptr[0] = value; + } + } + + ///ditto + auto ref back(size_t dimension = 0)() @property + if (dimension < N) + { + assert(!empty!dimension); + static if (PureN == 1) + { + return _ptr[backIndex]; + } + else + { + static if (hasElaborateAssign!PureRange) + ElemType ret; + else + ElemType ret = void; + foreach (i; Iota!(0, dimension)) + { + ret._lengths[i] = _lengths[i]; + ret._strides[i] = _strides[i]; + } + foreach (i; Iota!(dimension, PureN-1)) + { + ret._lengths[i] = _lengths[i + 1]; + ret._strides[i] = _strides[i + 1]; + } + ret._ptr = _ptr + backIndex!dimension; + return ret; + } + } + + static if (PureN == 1 && isMutable!DeepElemType && !hasAccessByRef) + { + ///ditto + auto back(size_t dimension = 0, T)(T value) @property + if (dimension == 0) + { + assert(!empty!dimension); + return _ptr[backIndex] = value; + } + } + + ///ditto + void popFront(size_t dimension = 0)() + if (dimension < N) + { + pragma(inline, true); + assert(_lengths[dimension], __FUNCTION__ ~ ": length!" ~ dimension.stringof ~ " should be greater than 0."); + _lengths[dimension]--; + _ptr += _strides[dimension]; + } + + ///ditto + void popBack(size_t dimension = 0)() + if (dimension < N) + { + pragma(inline, true); + assert(_lengths[dimension], __FUNCTION__ ~ ": length!" ~ dimension.stringof ~ " should be greater than 0."); + _lengths[dimension]--; + } + + ///ditto + void popFrontExactly(size_t dimension = 0)(size_t n) + if (dimension < N) + { + pragma(inline, true); + assert(n <= _lengths[dimension], __FUNCTION__ ~ ": n should be less than or equal to length!" ~ dimension.stringof); + _lengths[dimension] -= n; + _ptr += _strides[dimension] * n; + } + + ///ditto + void popBackExactly(size_t dimension = 0)(size_t n) + if (dimension < N) + { + pragma(inline, true); + assert(n <= _lengths[dimension], __FUNCTION__ ~ ": n should be less than or equal to length!" ~ dimension.stringof); + _lengths[dimension] -= n; + } + + ///ditto + void popFrontN(size_t dimension = 0)(size_t n) + if (dimension < N) + { + pragma(inline, true); + import std.algorithm.comparison: min; + popFrontExactly!dimension(min(n, _lengths[dimension])); + } + + ///ditto + void popBackN(size_t dimension = 0)(size_t n) + if (dimension < N) + { + pragma(inline, true); + import std.algorithm.comparison: min; + popBackExactly!dimension(min(n, _lengths[dimension])); + } + + static if (doUnittest) + /// + @safe @nogc pure nothrow unittest + { + import std.range: iota; + import std.range.primitives; + auto slice = 10000.iota.sliced(10, 20, 30); + + static assert(isRandomAccessRange!(typeof(slice))); + static assert(hasSlicing!(typeof(slice))); + static assert(hasLength!(typeof(slice))); + + assert(slice.shape == cast(size_t[3])[10, 20, 30]); + slice.popFront; + slice.popFront!1; + slice.popBackExactly!2(4); + assert(slice.shape == cast(size_t[3])[9, 19, 26]); + + auto matrix = slice.front!1; + assert(matrix.shape == cast(size_t[2])[9, 26]); + + auto column = matrix.back!1; + assert(column.shape == cast(size_t[1])[9]); + + slice.popFrontExactly!1(slice.length!1); + assert(slice.empty == false); + assert(slice.empty!1 == true); + assert(slice.empty!2 == false); + assert(slice.shape == cast(size_t[3])[9, 0, 26]); + + assert(slice.back.front!1.empty); + + slice.popFrontN!0(40); + slice.popFrontN!2(40); + assert(slice.shape == cast(size_t[3])[0, 0, 0]); + } + + package void popFront(size_t dimension) + { + assert(dimension < N, __FUNCTION__ ~ ": dimension should be less than N = " ~ N.stringof); + assert(_lengths[dimension], ": length!dim should be greater than 0."); + _lengths[dimension]--; + _ptr += _strides[dimension]; + } + + + package void popBack(size_t dimension) + { + assert(dimension < N, __FUNCTION__ ~ ": dimension should be less than N = " ~ N.stringof); + assert(_lengths[dimension], ": length!dim should be greater than 0."); + _lengths[dimension]--; + } + + package void popFrontExactly(size_t dimension, size_t n) + { + assert(dimension < N, __FUNCTION__ ~ ": dimension should be less than N = " ~ N.stringof); + assert(n <= _lengths[dimension], __FUNCTION__ ~ ": n should be less than or equal to length!dim"); + _lengths[dimension] -= n; + _ptr += _strides[dimension] * n; + } + + package void popBackExactly(size_t dimension, size_t n) + { + assert(dimension < N, __FUNCTION__ ~ ": dimension should be less than N = " ~ N.stringof); + assert(n <= _lengths[dimension], __FUNCTION__ ~ ": n should be less than or equal to length!dim"); + _lengths[dimension] -= n; + } + + package void popFrontN(size_t dimension, size_t n) + { + assert(dimension < N, __FUNCTION__ ~ ": dimension should be less than N = " ~ N.stringof); + import std.algorithm.comparison: min; + popFrontExactly(dimension, min(n, _lengths[dimension])); + } + + package void popBackN(size_t dimension, size_t n) + { + assert(dimension < N, __FUNCTION__ ~ ": dimension should be less than N = " ~ N.stringof); + import std.algorithm.comparison: min; + popBackExactly(dimension, min(n, _lengths[dimension])); + } + + /++ + Returns: total number of elements in a slice + +/ + size_t elementsCount() const + { + size_t len = 1; + foreach (i; Iota!(0, N)) + len *= _lengths[i]; + return len; + } + + static if (doUnittest) + /// Regular slice + @safe @nogc pure nothrow unittest + { + import std.range: iota; + assert(100.iota.sliced(3, 4, 5).elementsCount == 60); + } + + + static if (doUnittest) + /// Packed slice + @safe @nogc pure nothrow unittest + { + import std.experimental.ndslice.selection: pack, evertPack; + import std.range: iota; + auto slice = 50000.iota.sliced(3, 4, 5, 6, 7, 8); + auto p = slice.pack!2; + assert(p.elementsCount == 360); + assert(p[0, 0, 0, 0].elementsCount == 56); + assert(p.evertPack.elementsCount == 56); + } + + /++ + Overloading `==` and `!=` + +/ + bool opEquals(size_t NR, RangeR)(auto ref Slice!(NR, RangeR) rslice) + if (Slice!(NR, RangeR).PureN == PureN) + { + if (this._lengths != rslice._lengths) + return false; + static if ( + !hasReference!(typeof(this)) + && !hasReference!(typeof(rslice)) + && __traits(compiles, this._ptr == rslice._ptr) + ) + { + if (this._strides == rslice._strides && this._ptr == rslice._ptr) + return true; + } + return opEqualsImpl(this, rslice); + } + + ///ditto + bool opEquals(T)(T[] rarrary) + { + if (this.length != rarrary.length) + return false; + foreach(i, ref e; rarrary) + if(e != this[i]) + return false; + return true; + } + + static if (doUnittest) + /// + pure nothrow unittest + { + auto a = [1, 2, 3, 4].sliced(2, 2); + + assert(a != [1, 2, 3, 4, 5, 6].sliced(2, 3)); + assert(a != [[1, 2, 3], [4, 5, 6]]); + + assert(a == [1, 2, 3, 4].sliced(2, 2)); + assert(a == [[1, 2], [3, 4]]); + + assert(a != [9, 2, 3, 4].sliced(2, 2)); + assert(a != [[9, 2], [3, 4]]); + } + + _Slice opSlice(size_t dimension)(size_t i, size_t j) + if (dimension < N) + in { + assert(i <= j, + "Slice.opSlice!" ~ dimension.stringof ~ ": the left bound must be less than or equal to the right bound."); + assert(j - i <= _lengths[dimension], + "Slice.opSlice!" ~ dimension.stringof ~ + ": difference between the right and the left bounds must be less than or equal to the length of the given dimension."); + } + body + { + pragma(inline, true); + return typeof(return)(i, j); + } + + /++ + $(BLUE Fully defined index). + +/ + auto ref opIndex(Indexes...)(Indexes _indexes) + if (isFullPureIndex!Indexes) + { + static if (PureN == N) + return _ptr[indexStride(_indexes)]; + else + return DeepElemType(_lengths[N .. $], _strides[N .. $], _ptr + indexStride(_indexes)); + } + + static if(doUnittest) + /// + pure nothrow unittest + { + auto slice = new int[10].sliced(5, 2); + + auto p = &slice[1, 1]; + *p = 3; + assert(slice[1, 1] == 3); + + size_t[2] index = [1, 1]; + assert(slice[index] == 3); + } + + /++ + $(BLUE Partially or fully defined slice). + +/ + auto opIndex(Slices...)(Slices slices) + if (isPureSlice!Slices) + { + static if (Slices.length) + { + + enum size_t j(size_t n) = n - Filter!(isIndex, Slices[0 .. n+1]).length; + enum size_t F = PureIndexLength!Slices; + enum size_t S = Slices.length; + static assert(N-F > 0); + size_t stride; + static if (hasElaborateAssign!PureRange) + Slice!(N-F, Range) ret; + else + Slice!(N-F, Range) ret = void; + foreach (i, slice; slices) //static + { + static if (isIndex!(Slices[i])) + { + assert(slice < _lengths[i], "Slice.opIndex: index must be less than length"); + stride += _strides[i] * slice; + } + else + { + stride += _strides[i] * slice.i; + ret._lengths[j!i] = slice.j - slice.i; + ret._strides[j!i] = _strides[i]; + } + } + foreach (i; Iota!(S, PureN)) + { + ret._lengths[i - F] = _lengths[i]; + ret._strides[i - F] = _strides[i]; + } + ret._ptr = _ptr + stride; + return ret; + } + else + { + return this; + } + } + + static if(doUnittest) + /// + pure nothrow unittest + { + auto slice = new int[15].sliced(5, 3); + + /// Fully defined slice + assert(slice[] == slice); + auto sublice = slice[0..$-2, 1..$]; + + /// Partially defined slice + auto row = slice[3]; + auto col = slice[0..$, 1]; + } + + static if(doUnittest) + pure nothrow unittest + { + auto slice = new int[15].sliced!(ReplaceArrayWithPointer.no)(5, 3); + + /// Fully defined slice + assert(slice[] == slice); + auto sublice = slice[0..$-2, 1..$]; + + /// Partially defined slice + auto row = slice[3]; + auto col = slice[0..$, 1]; + } + + static if (isMutable!DeepElemType && PureN == N) + { + private void opIndexAssignImpl(string op, size_t RN, RRange, Slices...)(Slice!(RN, RRange) value, Slices slices) + if (isFullPureSlice!Slices + && RN <= ReturnType!(opIndex!Slices).N) + { + auto slice = this[slices]; + assert(slice._lengths[$ - RN .. $] == value._lengths, __FUNCTION__ ~ ": argument must have the corresponding shape."); + version(none) //future optimization + static if((isPointer!Range || isDynamicArray!Range) && (isPointer!RRange || isDynamicArray!RRange)) + { + enum d = slice.N - value.N; + foreach_reverse (i; Iota!(0, value.N)) + if (slice._lengths[i + d] == 1) + { + if (value._lengths[i] == 1) + { + static if (i != value.N - 1) + { + import std.experimental.ndslice.iteration: swapped; + slice = slice.swapped(i + d, slice.N - 1); + value = value.swapped(i , value.N - 1); + } + goto L1; + } + else + { + goto L2; + } + } + L1: + _indexAssign!(true, op)(slice, value); + return; + } + L2: + _indexAssign!(false, op)(slice, value); + } + + private void opIndexAssignImpl(string op, T, Slices...)(T[] value, Slices slices) + if (isFullPureSlice!Slices + && !isDynamicArray!DeepElemType + && DynamicArrayDimensionsCount!(T[]) <= ReturnType!(opIndex!Slices).N) + { + auto slice = this[slices]; + version(none) //future optimization + static if (isPointer!Range || isDynamicArray!Range) + { + if (slice._lengths[$-1] == 1) + { + _indexAssign!(true, op)(slice, value); + return; + } + } + _indexAssign!(false, op)(slice, value); + } + + private void opIndexAssignImpl(string op, T, Slices...)(T value, Slices slices) + if (isFullPureSlice!Slices + && (!isDynamicArray!T || isDynamicArray!DeepElemType) + && !is(T : Slice!(RN, RRange), size_t RN, RRange)) + { + auto slice = this[slices]; + version(none) //future optimization + static if (isPointer!Range || isDynamicArray!Range) + { + if (slice._lengths[$-1] == 1) + { + _indexAssign!(true, op)(slice, value); + return; + } + } + _indexAssign!(false, op)(slice, value); + } + + /++ + Assignment of a value of `Slice` type to a $(BLUE fully defined slice). + +/ + void opIndexAssign(size_t RN, RRange, Slices...)(Slice!(RN, RRange) value, Slices slices) + if (isFullPureSlice!Slices + && RN <= ReturnType!(opIndex!Slices).N) + { + opIndexAssignImpl!""(value, slices); + } + + static if(doUnittest) + /// + pure nothrow unittest + { + auto a = new int[6].sliced(2, 3); + auto b = [1, 2, 3, 4].sliced(2, 2); + + a[0..$, 0..$-1] = b; + assert(a == [[1, 2, 0], [3, 4, 0]]); + + a[0..$, 0..$-1] = b[0]; + assert(a == [[1, 2, 0], [1, 2, 0]]); + + a[1, 0..$-1] = b[1]; + assert(a[1] == [3, 4, 0]); + + a[1, 0..$-1][] = b[0]; + assert(a[1] == [1, 2, 0]); + } + + static if(doUnittest) + pure nothrow unittest + { + auto a = new int[6].sliced!(ReplaceArrayWithPointer.no)(2, 3); + auto b = [1, 2, 3, 4].sliced(2, 2); + + a[0..$, 0..$-1] = b; + assert(a == [[1, 2, 0], [3, 4, 0]]); + + a[0..$, 0..$-1] = b[0]; + assert(a == [[1, 2, 0], [1, 2, 0]]); + + a[1, 0..$-1] = b[1]; + assert(a[1] == [3, 4, 0]); + + a[1, 0..$-1][] = b[0]; + assert(a[1] == [1, 2, 0]); + } + + /++ + Assignment of a regular multidimensional array to a $(BLUE fully defined slice). + +/ + void opIndexAssign(T, Slices...)(T[] value, Slices slices) + if (isFullPureSlice!Slices + && !isDynamicArray!DeepElemType + && DynamicArrayDimensionsCount!(T[]) <= ReturnType!(opIndex!Slices).N) + { + opIndexAssignImpl!""(value, slices); + } + + static if(doUnittest) + /// + pure nothrow unittest + { + auto a = new int[6].sliced(2, 3); + auto b = [[1, 2], [3, 4]]; + + a[] = [[1, 2, 3], [4, 5, 6]]; + assert(a == [[1, 2, 3], [4, 5, 6]]); + + a[0..$, 0..$-1] = [[1, 2], [3, 4]]; + assert(a == [[1, 2, 3], [3, 4, 6]]); + + a[0..$, 0..$-1] = [1, 2]; + assert(a == [[1, 2, 3], [1, 2, 6]]); + + a[1, 0..$-1] = [3, 4]; + assert(a[1] == [3, 4, 6]); + + a[1, 0..$-1][] = [3, 4]; + assert(a[1] == [3, 4, 6]); + } + + static if(doUnittest) + pure nothrow unittest + { + auto a = new int[6].sliced!(ReplaceArrayWithPointer.no)(2, 3); + auto b = [[1, 2], [3, 4]]; + + a[] = [[1, 2, 3], [4, 5, 6]]; + assert(a == [[1, 2, 3], [4, 5, 6]]); + + a[0..$, 0..$-1] = [[1, 2], [3, 4]]; + assert(a == [[1, 2, 3], [3, 4, 6]]); + + a[0..$, 0..$-1] = [1, 2]; + assert(a == [[1, 2, 3], [1, 2, 6]]); + + a[1, 0..$-1] = [3, 4]; + assert(a[1] == [3, 4, 6]); + + a[1, 0..$-1][] = [3, 4]; + assert(a[1] == [3, 4, 6]); + } + + /++ + Assignment of a value (e.g. a number) to a $(BLUE fully defined slice). + +/ + void opIndexAssign(T, Slices...)(T value, Slices slices) + if (isFullPureSlice!Slices + && (!isDynamicArray!T || isDynamicArray!DeepElemType) + && !is(T : Slice!(RN, RRange), size_t RN, RRange)) + { + opIndexAssignImpl!""(value, slices); + } + + static if(doUnittest) + /// + pure nothrow unittest + { + auto a = new int[6].sliced(2, 3); + + a[] = 9; + assert(a == [[9, 9, 9], [9, 9, 9]]); + + a[0..$, 0..$-1] = 1; + assert(a == [[1, 1, 9], [1, 1, 9]]); + + a[0..$, 0..$-1] = 2; + assert(a == [[2, 2, 9], [2, 2, 9]]); + + a[1, 0..$-1] = 3; + assert(a[1] == [3, 3, 9]); + + a[1, 0..$-1] = 4; + assert(a[1] == [4, 4, 9]); + + a[1, 0..$-1][] = 5; + assert(a[1] == [5, 5, 9]); + } + + static if(doUnittest) + pure nothrow unittest + { + auto a = new int[6].sliced!(ReplaceArrayWithPointer.no)(2, 3); + + a[] = 9; + assert(a == [[9, 9, 9], [9, 9, 9]]); + + a[0..$, 0..$-1] = 1; + assert(a == [[1, 1, 9], [1, 1, 9]]); + + a[0..$, 0..$-1] = 2; + assert(a == [[2, 2, 9], [2, 2, 9]]); + + a[1, 0..$-1] = 3; + assert(a[1] == [3, 3, 9]); + + a[1, 0..$-1] = 4; + assert(a[1] == [4, 4, 9]); + + a[1, 0..$-1][] = 5; + assert(a[1] == [5, 5, 9]); + } + + /++ + Assignment of a value (e.g. a number) to a $(BLUE fully defined index). + +/ + auto ref opIndexAssign(T, Indexes...)(T value, Indexes _indexes) + if (isFullPureIndex!Indexes) + { + return _ptr[indexStride(_indexes)] = value; + } + + static if(doUnittest) + /// + pure nothrow unittest + { + auto a = new int[6].sliced(2, 3); + + a[1, 2] = 3; + assert(a[1, 2] == 3); + } + + static if(doUnittest) + pure nothrow unittest + { + auto a = new int[6].sliced!(ReplaceArrayWithPointer.no)(2, 3); + + a[1, 2] = 3; + assert(a[1, 2] == 3); + } + + /++ + Op Assignment `op=` of a value (e.g. a number) to a $(BLUE fully defined index). + +/ + auto ref opIndexOpAssign(string op, T, Indexes...)(T value, Indexes _indexes) + if (isFullPureIndex!Indexes) + { + mixin (`return _ptr[indexStride(_indexes)] ` ~ op ~ `= value;`); + } + + static if(doUnittest) + /// + pure nothrow unittest + { + auto a = new int[6].sliced(2, 3); + + a[1, 2] += 3; + assert(a[1, 2] == 3); + } + + static if(doUnittest) + pure nothrow unittest + { + auto a = new int[6].sliced!(ReplaceArrayWithPointer.no)(2, 3); + + a[1, 2] += 3; + assert(a[1, 2] == 3); + } + + /++ + Op Assignment `op=` of a value of `Slice` type to a $(BLUE fully defined slice). + +/ + void opIndexOpAssign(string op, size_t RN, RRange, Slices...)(Slice!(RN, RRange) value, Slices slices) + if (isFullPureSlice!Slices + && RN <= ReturnType!(opIndex!Slices).N) + { + opIndexAssignImpl!op(value, slices); + } + + static if(doUnittest) + /// + pure nothrow unittest + { + auto a = new int[6].sliced(2, 3); + auto b = [1, 2, 3, 4].sliced(2, 2); + + a[0..$, 0..$-1] += b; + assert(a == [[1, 2, 0], [3, 4, 0]]); + + a[0..$, 0..$-1] += b[0]; + assert(a == [[2, 4, 0], [4, 6, 0]]); + + a[1, 0..$-1] += b[1]; + assert(a[1] == [7, 10, 0]); + + a[1, 0..$-1][] += b[0]; + assert(a[1] == [8, 12, 0]); + } + + static if(doUnittest) + pure nothrow unittest + { + auto a = new int[6].sliced!(ReplaceArrayWithPointer.no)(2, 3); + auto b = [1, 2, 3, 4].sliced(2, 2); + + a[0..$, 0..$-1] += b; + assert(a == [[1, 2, 0], [3, 4, 0]]); + + a[0..$, 0..$-1] += b[0]; + assert(a == [[2, 4, 0], [4, 6, 0]]); + + a[1, 0..$-1] += b[1]; + assert(a[1] == [7, 10, 0]); + + a[1, 0..$-1][] += b[0]; + assert(a[1] == [8, 12, 0]); + } + + /++ + Op Assignment `op=` of a regular multidimensional array to a $(BLUE fully defined slice). + +/ + void opIndexOpAssign(string op, T, Slices...)(T[] value, Slices slices) + if (isFullPureSlice!Slices + && !isDynamicArray!DeepElemType + && DynamicArrayDimensionsCount!(T[]) <= ReturnType!(opIndex!Slices).N) + { + opIndexAssignImpl!op(value, slices); + } + + static if(doUnittest) + /// + pure nothrow unittest + { + auto a = new int[6].sliced(2, 3); + + a[0..$, 0..$-1] += [[1, 2], [3, 4]]; + assert(a == [[1, 2, 0], [3, 4, 0]]); + + a[0..$, 0..$-1] += [1, 2]; + assert(a == [[2, 4, 0], [4, 6, 0]]); + + a[1, 0..$-1] += [3, 4]; + assert(a[1] == [7, 10, 0]); + + a[1, 0..$-1][] += [1, 2]; + assert(a[1] == [8, 12, 0]); + } + + static if(doUnittest) + pure nothrow unittest + { + auto a = new int[6].sliced!(ReplaceArrayWithPointer.no)(2, 3); + + a[0..$, 0..$-1] += [[1, 2], [3, 4]]; + assert(a == [[1, 2, 0], [3, 4, 0]]); + + a[0..$, 0..$-1] += [1, 2]; + assert(a == [[2, 4, 0], [4, 6, 0]]); + + a[1, 0..$-1] += [3, 4]; + assert(a[1] == [7, 10, 0]); + + a[1, 0..$-1][] += [1, 2]; + assert(a[1] == [8, 12, 0]); + } + + /++ + Op Assignment `op=` of a value (e.g. a number) to a $(BLUE fully defined slice). + +/ + void opIndexOpAssign(string op, T, Slices...)(T value, Slices slices) + if (isFullPureSlice!Slices + && (!isDynamicArray!T || isDynamicArray!DeepElemType) + && !is(T : Slice!(RN, RRange), size_t RN, RRange)) + { + opIndexAssignImpl!op(value, slices); + } + + static if(doUnittest) + /// + pure nothrow unittest + { + auto a = new int[6].sliced(2, 3); + + a[] += 1; + assert(a == [[1, 1, 1], [1, 1, 1]]); + + a[0..$, 0..$-1] += 2; + assert(a == [[3, 3, 1], [3, 3, 1]]); + + a[1, 0..$-1] += 3; + assert(a[1] == [6, 6, 1]); + } + + static if(doUnittest) + pure nothrow unittest + { + auto a = new int[6].sliced!(ReplaceArrayWithPointer.no)(2, 3); + + a[] += 1; + assert(a == [[1, 1, 1], [1, 1, 1]]); + + a[0..$, 0..$-1] += 2; + assert(a == [[3, 3, 1], [3, 3, 1]]); + + a[1, 0..$-1] += 3; + assert(a[1] == [6, 6, 1]); + } + + /++ + Increment `++` and Decrement `--` operators for a $(BLUE fully defined index). + +/ + auto ref opIndexUnary(string op, Indexes...)(Indexes _indexes) + if (isFullPureIndex!Indexes && (op == `++` || op == `--`)) + { + mixin (`return ` ~ op ~ `_ptr[indexStride(_indexes)];`); + } + + static if(doUnittest) + /// + pure nothrow unittest + { + auto a = new int[6].sliced(2, 3); + + ++a[1, 2]; + assert(a[1, 2] == 1); + } + + static if(doUnittest) + pure nothrow unittest + { + auto a = new int[6].sliced!(ReplaceArrayWithPointer.no)(2, 3); + + ++a[1, 2]; + assert(a[1, 2] == 1); + } + + /++ + Increment `++` and Decrement `--` operators for a $(BLUE fully defined slice). + +/ + void opIndexUnary(string op, Slices...)(Slices slices) + if (isFullPureSlice!Slices && (op == `++` || op == `--`)) + { + auto sl = this[slices]; + static if (sl.N == 1) + { + for (; sl.length; sl.popFront) + { + mixin (op ~ `sl.front;`); + } + } + else + { + foreach (v; sl) + { + mixin (op ~ `v[];`); + } + } + } + + static if(doUnittest) + /// + pure nothrow unittest + { + auto a = new int[6].sliced(2, 3); + + ++a[]; + assert(a == [[1, 1, 1], [1, 1, 1]]); + + --a[1, 0..$-1]; + assert(a[1] == [0, 0, 1]); + } + + static if(doUnittest) + pure nothrow unittest + { + auto a = new int[6].sliced!(ReplaceArrayWithPointer.no)(2, 3); + + ++a[]; + assert(a == [[1, 1, 1], [1, 1, 1]]); + + --a[1, 0..$-1]; + assert(a[1] == [0, 0, 1]); + } + } +} + + +/++ +Slicing, indexing, and arithmetic operations. ++/ +pure nothrow unittest +{ + import std.array: array; + import std.range: iota; + import std.experimental.ndslice.iteration: transposed; + + auto tensor = 60.iota.array.sliced(3, 4, 5); + + assert(tensor[1, 2] == tensor[1][2]); + assert(tensor[1, 2, 3] == tensor[1][2][3]); + + assert( tensor[0..$, 0..$, 4] == tensor.transposed!2[4]); + assert(&tensor[0..$, 0..$, 4][1, 2] is &tensor[1, 2, 4]); + + tensor[1, 2, 3]++; //`opIndex` returns value by reference. + --tensor[1, 2, 3]; //`opUnary` + + ++tensor[]; + tensor[] -= 1; + + // `opIndexAssing` accepts only fully defined indexes and slices. + // Use an additional empty slice `[]`. + static assert(!__traits(compiles), tensor[0 .. 2] *= 2); + + tensor[0 .. 2][] *= 2; //OK, empty slice + tensor[0 .. 2, 3, 0..$] /= 2; //OK, 3 index or slice positions are defined. + + //fully defined index may be replaced by a static array + size_t[3] index = [1, 2, 3]; + assert(tensor[index] == tensor[1, 2, 3]); +} + +/++ +Operations with rvalue slices. ++/ +pure nothrow unittest +{ + import std.experimental.ndslice.iteration: transposed, everted; + + auto tensor = new int[60].sliced(3, 4, 5); + auto matrix = new int[12].sliced(3, 4); + auto vector = new int[ 3].sliced(3); + + foreach (i; 0..3) + vector[i] = i; + + // fills matrix columns + matrix.transposed[] = vector; + + // fills tensor with vector + // transposed tensor shape is (4, 5, 3) + // vector shape is ( 3) + tensor.transposed!(1, 2)[] = vector; + + + // transposed tensor shape is (5, 3, 4) + // matrix shape is ( 3, 4) + tensor.transposed!2[] += matrix; + + // transposed tensor shape is (5, 4, 3) + // transposed matrix shape is ( 4, 3) + tensor.everted[] ^= matrix.transposed; // XOR +} + +/++ +Creating a slice from text. +See also $(LINK2 std_format.html, std.format). ++/ +unittest +{ + import std.algorithm, std.conv, std.exception, std.format, + std.functional, std.string, std.range; + + Slice!(2, int*) toMatrix(string str) + { + string[][] data = str.lineSplitter.filter!(not!empty).map!split.array; + + size_t rows = data .length.enforce("empty input"); + size_t columns = data[0].length.enforce("empty first row"); + + data.each!(a => enforce(a.length == columns, "rows have different lengths")); + + auto slice = new int[rows * columns].sliced(rows, columns); + foreach (i, line; data) + foreach (j, num; line) + slice[i, j] = num.to!int; + return slice; + } + + auto input = "\r1 2 3\r\n 4 5 6\n"; + + auto matrix = toMatrix(input); + assert(matrix == [[1, 2, 3], [4, 5, 6]]); + + // back to text + auto text2 = format("%(%(%s %)\n%)\n", matrix); + assert(text2 == "1 2 3\n4 5 6\n"); +} + +// Slicing +@safe @nogc pure nothrow unittest +{ + import std.range: iota; + auto a = 1000000.iota.sliced(10, 20, 30, 40); + auto b = a[0..$, 10, 4 .. 27, 4]; + auto c = b[2 .. 9, 5 .. 10]; + auto d = b[3..$, $-2]; + assert(b[4, 17] == a[4, 10, 21, 4]); + assert(c[1, 2] == a[3, 10, 11, 4]); + assert(d[3] == a[6, 10, 25, 4]); +} + +// Operator overloading. # 1 +pure nothrow unittest +{ + import std.range: iota; + import std.array: array; + auto fun(ref int x) { x *= 3; } + + auto tensor = 1000 + .iota + .array + .sliced(8, 9, 10); + + ++tensor[]; + fun(tensor[0, 0, 0]); + + assert(tensor[0, 0, 0] == 3); + + tensor[0, 0, 0] *= 4; + tensor[0, 0, 0]--; + assert(tensor[0, 0, 0] == 11); +} + +// Operator overloading. # 2 +pure nothrow unittest +{ + import std.algorithm.iteration: map; + import std.array: array; + import std.bigint; + import std.range: iota; + + auto matrix = 100 + .iota + .map!(i => BigInt(i)) + .array + .sliced(8, 9); + + matrix[3 .. 6, 2] += 100; + foreach (i; 0 .. 8) + foreach (j; 0 .. 9) + if (i >= 3 && i < 6 && j == 2) + assert(matrix[i, j] >= 100); + else + assert(matrix[i, j] < 100); +} + +// Operator overloading. # 3 +pure nothrow unittest +{ + import std.algorithm.iteration: map; + import std.array: array; + import std.range: iota; + + auto matrix = 100 + .iota + .array + .sliced(8, 9); + + matrix[] = matrix; + matrix[] += matrix; + assert(matrix[2, 3] == (2 * 9 + 3) * 2); + + auto vec = iota(100, 200).sliced(9); + matrix[] = vec; + foreach (v; matrix) + assert(v == vec); + + matrix[] += vec; + foreach (vector; matrix) + foreach (elem; vector) + assert(elem >= 200); +} + +// Type deduction +unittest +{ + // Arrays + foreach (T; AliasSeq!(int, const int, immutable int)) + static assert(is(typeof((T[]).init.sliced(3, 4)) == Slice!(2, T*))); + + // Container Array + import std.container.array; + Array!int ar; + static assert(is(typeof(ar[].sliced(3, 4)) == Slice!(2, typeof(ar[])))); + + // Implicit conversion of a range to its unqualified type. + import std.range: iota; + auto i0 = 100.iota; + const i1 = 100.iota; + immutable i2 = 100.iota; + alias S = Slice!(3, typeof(iota(0))); + foreach (i; AliasSeq!(i0, i1, i2)) + static assert(is(typeof(i.sliced(3, 4, 5)) == S)); +} + +// Test for map #1 +unittest +{ + import std.algorithm.iteration: map; + import std.range.primitives; + auto slice = [1, 2, 3, 4].sliced(2, 2); + + auto r = slice.map!(a => a.map!(a => a * 6)); + assert(r.front.front == 6); + assert(r.front.back == 12); + assert(r.back.front == 18); + assert(r.back.back == 24); + assert(r[0][0] == 6); + assert(r[0][1] == 12); + assert(r[1][0] == 18); + assert(r[1][1] == 24); + static assert(hasSlicing!(typeof(r))); + static assert(isForwardRange!(typeof(r))); + static assert(isRandomAccessRange!(typeof(r))); + +} + +// Test for map #2 +unittest +{ + import std.algorithm.iteration: map; + import std.range.primitives; + auto data = [1, 2, 3, 4].map!(a => a * 2); + static assert(hasSlicing!(typeof(data))); + static assert(isForwardRange!(typeof(data))); + static assert(isRandomAccessRange!(typeof(data))); + auto slice = data.sliced(2, 2); + static assert(hasSlicing!(typeof(slice))); + static assert(isForwardRange!(typeof(slice))); + static assert(isRandomAccessRange!(typeof(slice))); + auto r = slice.map!(a => a.map!(a => a * 3)); + static assert(hasSlicing!(typeof(r))); + static assert(isForwardRange!(typeof(r))); + static assert(isRandomAccessRange!(typeof(r))); + assert(r.front.front == 6); + assert(r.front.back == 12); + assert(r.back.front == 18); + assert(r.back.back == 24); + assert(r[0][0] == 6); + assert(r[0][1] == 12); + assert(r[1][0] == 18); + assert(r[1][1] == 24); +} + +private bool opEqualsImpl + (size_t NL, RangeL, size_t NR, RangeR)( + auto ref Slice!(NL, RangeL) ls, + auto ref Slice!(NR, RangeR) rs) +in +{ + assert(ls._lengths == rs._lengths); +} +body +{ + foreach (i; 0 .. ls.length) + { + static if (Slice!(NL, RangeL).PureN == 1) + { + if (ls[i] != rs[i]) + return false; + } + else + { + if (!opEqualsImpl(ls[i], rs[i])) + return false; + } + } + return true; +} + +private struct PtrShell(Range) +{ + sizediff_t _shift; + Range _range; + + enum hasAccessByRef = isPointer!Range || + __traits(compiles, { auto a = &(_range[0]); } ); + + void opOpAssign(string op)(sizediff_t shift) + if (op == `+` || op == `-`) + { + pragma(inline, true); + mixin (`_shift ` ~ op ~ `= shift;`); + } + + auto opBinary(string op)(sizediff_t shift) + if (op == `+` || op == `-`) + { + mixin (`return typeof(this)(_shift ` ~ op ~ ` shift, _range);`); + } + + auto ref opIndex(sizediff_t index) + in + { + assert(_shift + index >= 0); + import std.range.primitives: hasLength; + static if (hasLength!Range) + assert(_shift + index <= _range.length); + } + body + { + return _range[_shift + index]; + } + + static if (!hasAccessByRef) + { + auto ref opIndexAssign(T)(T value, sizediff_t index) + in + { + assert(_shift + index >= 0); + static if (hasLength!Range) + assert(_shift + index <= _range.length); + } + body + { + return _range[_shift + index] = value; + } + + auto ref opIndexOpAssign(string op, T)(T value, sizediff_t index) + in + { + assert(_shift + index >= 0); + static if (hasLength!Range) + assert(_shift + index <= _range.length); + } + body + { + mixin (`return _range[_shift + index] ` ~ op ~ `= value;`); + } + } + + static if (canSave!Range) + auto save() @property + { + static if (isDynamicArray!Range) + return typeof(this)(_shift, _range); + else + return typeof(this)(_shift, _range.save); + } +} + +private auto ptrShell(Range)(Range range, sizediff_t shift = 0) +{ + return PtrShell!Range(shift, range); +} + +version(none) // TODO: Remove before merge +// @safe pure nothrow ? +unittest +{ + import std.internal.test.dummyrange; + foreach (RB; AliasSeq!(ReturnBy.Reference, ReturnBy.Value)) + { + DummyRange!(RB, Length.Yes, RangeType.Random) range; + assert(range.length >= 10); + auto ptr = range.ptrShell; + assert(ptr[0] == range[0]); + auto save0 = range[0]; + ptr[0] += 10; + ++ptr[0]; + assert(ptr[0] == save0 + 11); + (ptr + 5)[2] = 333; + assert(range[7] == 333); + } +} + +private enum isSlicePointer(T) = isPointer!T || is(T : PtrShell!R, R); + +private struct LikePtr {} + +package template hasPtrBehavior(T) +{ + static if (isPointer!T) + enum hasPtrBehavior = true; + else + static if (!isAggregateType!T) + enum hasPtrBehavior = false; + else + enum hasPtrBehavior = hasUDA!(T, LikePtr); +} + +private template PtrTuple(Names...) +{ + @LikePtr struct PtrTuple(Ptrs...) + if (allSatisfy!(isSlicePointer, Ptrs) && Ptrs.length == Names.length) + { + Ptrs ptrs; + + static if (allSatisfy!(canSave, Ptrs)) + auto save() @property + { + static if (anySatisfy!(hasElaborateAssign, Ptrs)) + PtrTuple p; + else + PtrTuple p = void; + foreach (i, ref ptr; ptrs) + static if (isPointer!(Ptrs[i])) + p.ptrs[i] = ptr; + else + p.ptrs[i] = ptr.save; + + return p; + } + + void opOpAssign(string op)(sizediff_t shift) + if (op == `+` || op == `-`) + { + foreach (ref ptr; ptrs) + mixin (`ptr ` ~ op ~ `= shift;`); + } + + auto opBinary(string op)(sizediff_t shift) + if (op == `+` || op == `-`) + { + auto ret = this.ptrs; + ret.opOpAssign!op(shift); + return ret; + } + + public struct Index + { + Ptrs _ptrs__; + mixin (PtrTupleFrontMembers!Names); + } + + auto opIndex(sizediff_t index) + { + auto p = ptrs; + foreach (ref ptr; p) + ptr += index; + return Index(p); + } + + auto front() @property + { + return Index(ptrs); + } + } +} + +pure nothrow unittest +{ + auto a = new int[20], b = new int[20]; + import std.stdio; + alias T = PtrTuple!("a", "b"); + alias S = T!(int*, int*); + auto t = S(a.ptr, b.ptr); + t[4].a++; + auto r = t[4]; + r.b = r.a * 2; + assert(b[4] == 2); + t.front.a++; + r = t.front; + r.b = r.a * 2; + assert(b[0] == 2); +} + +private template PtrTupleFrontMembers(Names...) + if (Names.length <= 32) +{ + static if (Names.length) + { + alias Top = Names[0..$-1]; + enum int m = Top.length; + enum PtrTupleFrontMembers = PtrTupleFrontMembers!Top + ~ " + @property auto ref " ~ Names[$-1] ~ "() { + static if (__traits(compiles,{ auto _f = _ptrs__[" ~ m.stringof ~ "].front; })) + return _ptrs__[" ~ m.stringof ~ "].front; + else + return _ptrs__[" ~ m.stringof ~ "][0]; + } + "; + } + else + { + enum PtrTupleFrontMembers = ""; + } +} + + +private template PrepareRangeType(Range) +{ + static if (isPointer!Range) + alias PrepareRangeType = Range; + else + alias PrepareRangeType = PtrShell!Range; +} + +private enum bool isType(alias T) = false; + +private enum bool isType(T) = true; + +private enum isStringValue(alias T) = is(typeof(T) : string); + +private void _indexAssign(bool lastStrideEquals1, string op, size_t N, size_t RN, Range, RRange)(Slice!(N, Range) slice, Slice!(RN, RRange) value) + if (N >= RN) +{ + static if (N == 1) + { + static if(lastStrideEquals1 && (isPointer!Range || isDynamicArray!Range) && (isPointer!RRange || isDynamicArray!RRange)) + { + static if(isPointer!Range) + auto l = slice._ptr; + else + auto l = slice._ptr._range[slice._ptr._shift .. slice._ptr._shift + slice._lengths[0]]; + static if(isPointer!RRange) + auto r = value._ptr; + else + auto r = value._ptr._range[value._ptr._shift .. value._ptr._shift + value._lengths[0]]; + auto len = slice._lengths[0]; + for (size_t i; i < len; i++) + { + mixin("l[i]" ~ op ~ "= r[i];"); + } + } + else + { + while (slice._lengths[0]) + { + mixin("slice.front " ~ op ~ "= value.front;"); + slice.popFront; + value.popFront; + } + } + } + else + static if (N == RN) + { + while (slice._lengths[0]) + { + _indexAssign!(lastStrideEquals1, op)(slice.front, value.front); + slice.popFront; + value.popFront; + } + } + else + { + while (slice._lengths[0]) + { + _indexAssign!(lastStrideEquals1, op)(slice.front, value); + slice.popFront; + } + } +} + +private void _indexAssign(bool lastStrideEquals1, string op, size_t N, Range, T)(Slice!(N, Range) slice, T[] value) + if (DynamicArrayDimensionsCount!(T[]) <= N) +{ + assert(slice.length == value.length, __FUNCTION__ ~ ": argument must have the same length."); + static if (N == 1) + { + static if(lastStrideEquals1 && (isPointer!Range || isDynamicArray!Range)) + { + static if(isPointer!Range) + auto l = slice._ptr; + else + auto l = slice._ptr._range[slice._ptr._shift .. slice._ptr._shift + slice._lengths[0]]; + auto r = value; + auto len = slice._lengths[0]; + for (size_t i; i < len; i++) + { + mixin("l[i]" ~ op ~ "= r[i];"); + } + } + else + { + while (slice._lengths[0]) + { + mixin("slice.front " ~ op ~ "= value[0];"); + slice.popFront; + value = value[1..$]; + } + } + } + else + static if (N == DynamicArrayDimensionsCount!(T[])) + { + while (slice._lengths[0]) + { + _indexAssign!(lastStrideEquals1, op)(slice.front, value[0]); + slice.popFront; + value = value[1 .. $]; + } + } + else + { + while (slice._lengths[0]) + { + _indexAssign!(lastStrideEquals1, op)(slice.front, value); + slice.popFront; + } + } +} + +private void _indexAssign(bool lastStrideEquals1, string op, size_t N, Range, T)(Slice!(N, Range) slice, T value) + if ((!isDynamicArray!T || isDynamicArray!(Slice!(N, Range).DeepElemType)) + && !is(T : Slice!(RN, RRange), size_t RN, RRange)) +{ + static if (N == 1) + { + static if(lastStrideEquals1 && (isPointer!Range || isDynamicArray!Range)) + { + static if(isPointer!Range) + auto l = slice._ptr; + else + auto l = slice._ptr._range[slice._ptr._shift .. $]; + auto len = slice._lengths[0]; + for (size_t i; i < len; i++) + { + mixin("l[i]" ~ op ~ "= value;"); + } + } + else + { + while (slice._lengths[0]) + { + mixin("slice.front " ~ op ~ "= value;"); + slice.popFront; + } + } + } + else + { + while (slice._lengths[0]) + { + _indexAssign!(lastStrideEquals1, op)(slice.front, value); + slice.popFront; + } + } +} diff --git a/win32.mak b/win32.mak index 73c027a12ab..d795773123c 100644 --- a/win32.mak +++ b/win32.mak @@ -189,6 +189,12 @@ SRC_STD_REGEX= std\regex\internal\ir.d std\regex\package.d std\regex\internal\pa SRC_STD_RANGE= std\range\package.d std\range\primitives.d \ std\range\interfaces.d +SRC_STD_NDSLICE= std\experimental\ndslice\package.d \ + std\experimental\ndslice\iteration.d \ + std\experimental\ndslice\selection.d \ + std\experimental\ndslice\slice.d \ + std\experimental\ndslice\internal.d + SRC_STD_NET= std\net\isemail.d std\net\curl.d SRC_STD_C= std\c\process.d std\c\stdlib.d std\c\time.d std\c\stdio.d \ @@ -243,6 +249,7 @@ SRC_TO_COMPILE_NOT_STD= \ SRC_TO_COMPILE= $(SRC_STD_ALL) \ $(SRC_STD_ALGO) \ $(SRC_STD_RANGE) \ + $(SRC_STD_NDSLICE) \ $(SRC_TO_COMPILE_NOT_STD) SRC_ZLIB= \ @@ -425,6 +432,7 @@ UNITTEST_OBJS= \ unittest : $(LIB) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest1.obj $(SRC_STD_1_HEAVY) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest2.obj $(SRC_STD_RANGE) + $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest2.obj $(SRC_STD_NDSLICE) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest2a.obj $(SRC_STD_2a_HEAVY) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest3.obj $(SRC_STD_3) $(DMD) $(UDFLAGS) -L/co -c -unittest -ofunittest3a.obj $(SRC_STD_3a) @@ -915,7 +923,7 @@ zip : win32.mak win64.mak posix.mak osmodel.mak $(STDDOC) $(SRC) \ $(SRC_STD_C_WIN) $(SRC_STD_C_LINUX) $(SRC_STD_C_OSX) $(SRC_STD_C_FREEBSD) \ $(SRC_ETC) $(SRC_ETC_C) $(SRC_ZLIB) $(SRC_STD_NET) $(SRC_STD_DIGEST) $(SRC_STD_CONTAINER) \ $(SRC_STD_INTERNAL) $(SRC_STD_INTERNAL_DIGEST) $(SRC_STD_INTERNAL_MATH) \ - $(SRC_STD_INTERNAL_WINDOWS) $(SRC_STD_REGEX) $(SRC_STD_RANGE) $(SRC_STD_ALGO) \ + $(SRC_STD_INTERNAL_WINDOWS) $(SRC_STD_REGEX) $(SRC_STD_RANGE) $(SRC_STD_NDSLICE) $(SRC_STD_ALGO) \ $(SRC_STD_LOGGER) del phobos.zip zip32 -u phobos win32.mak win64.mak posix.mak osmodel.mak $(STDDOC) @@ -939,6 +947,7 @@ zip : win32.mak win64.mak posix.mak osmodel.mak $(STDDOC) $(SRC) \ zip32 -u phobos $(SRC_STD_CONTAINER) zip32 -u phobos $(SRC_STD_REGEX) zip32 -u phobos $(SRC_STD_RANGE) + zip32 -u phobos $(SRC_STD_NDSLICE) zip32 -u phobos $(SRC_STD_ALGO) phobos.zip : zip diff --git a/win64.mak b/win64.mak index fdadbfd86a7..b02bdbc8a1f 100644 --- a/win64.mak +++ b/win64.mak @@ -212,6 +212,12 @@ SRC_STD_REGEX= std\regex\internal\ir.d std\regex\package.d std\regex\internal\pa SRC_STD_RANGE= std\range\package.d std\range\primitives.d \ std\range\interfaces.d +SRC_STD_NDSLICE= std\experimental\ndslice\package.d \ + std\experimental\ndslice\iteration.d \ + std\experimental\ndslice\selection.d \ + std\experimental\ndslice\slice.d \ + std\experimental\ndslice\internal.d + SRC_STD_NET= std\net\isemail.d std\net\curl.d SRC_STD_C= std\c\process.d std\c\stdlib.d std\c\time.d std\c\stdio.d \ @@ -266,6 +272,7 @@ SRC_TO_COMPILE_NOT_STD= \ SRC_TO_COMPILE= $(SRC_STD_ALL) \ $(SRC_STD_ALGO) \ $(SRC_STD_RANGE) \ + $(SRC_STD_NDSLICE) \ $(SRC_TO_COMPILE_NOT_STD) SRC_ZLIB= \ @@ -460,6 +467,7 @@ UNITTEST_OBJS= \ unittest : $(LIB) $(DMD) $(UDFLAGS) -c -unittest -ofunittest1.obj $(SRC_STD_1_HEAVY) $(DMD) $(UDFLAGS) -c -unittest -ofunittest2.obj $(SRC_STD_RANGE) + $(DMD) $(UDFLAGS) -c -unittest -ofunittest2.obj $(SRC_STD_NDSLICE) $(DMD) $(UDFLAGS) -c -unittest -ofunittest2a.obj $(SRC_STD_2a_HEAVY) $(DMD) $(UDFLAGS) -c -unittest -ofunittestM.obj $(SRC_STD_math) $(DMD) $(UDFLAGS) -c -unittest -ofunittest3.obj $(SRC_STD_3) @@ -893,7 +901,7 @@ zip : win32.mak win64.mak posix.mak osmodel.mak $(STDDOC) $(SRC) \ $(SRC_STD_C_WIN) $(SRC_STD_C_LINUX) $(SRC_STD_C_OSX) $(SRC_STD_C_FREEBSD) \ $(SRC_ETC) $(SRC_ETC_C) $(SRC_ZLIB) $(SRC_STD_NET) $(SRC_STD_DIGEST) $(SRC_STD_CONTAINER) \ $(SRC_STD_INTERNAL) $(SRC_STD_INTERNAL_DIGEST) $(SRC_STD_INTERNAL_MATH) \ - $(SRC_STD_INTERNAL_WINDOWS) $(SRC_STD_REGEX) $(SRC_STD_RANGE) $(SRC_STD_ALGO) \ + $(SRC_STD_INTERNAL_WINDOWS) $(SRC_STD_REGEX) $(SRC_STD_RANGE) $(SRC_STD_NDSLICE) $(SRC_STD_ALGO) \ $(SRC_STD_LOGGER) del phobos.zip zip32 -u phobos win32.mak win64.mak posix.mak osmodel.mak $(STDDOC) @@ -917,6 +925,7 @@ zip : win32.mak win64.mak posix.mak osmodel.mak $(STDDOC) $(SRC) \ zip32 -u phobos $(SRC_STD_CONTAINER) zip32 -u phobos $(SRC_STD_REGEX) zip32 -u phobos $(SRC_STD_RANGE) + zip32 -u phobos $(SRC_STD_NDSLICE) zip32 -u phobos $(SRC_STD_ALGO) phobos.zip : zip