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