digitalmars.D.learn - problem with opIndex
- Joseph (100/100) Sep 29 2017 I am trying to have a multi-dimensional array and opIndex has to
- H. S. Teoh (39/60) Sep 29 2017 [...]
- Ilya Yaroshenko (4/7) Sep 29 2017 You may want to look into ndslice package source code [1] --Ilya
I am trying to have a multi-dimensional array and opIndex has to have both an arbitrary number of parameters and allow for slicing. The problem is if I create opIndex for non-slicing, it looks like ref auto opIndex(T...)(T index) and this one catches all templates. But slices do not return the same value. I need to somehow have two opIndex's, one for slice handling and one for non-slices. Trying to accomplish arr[1..4, 5..8, 3, 3..4] I already have the single index case arr[3, 4, 3, 1] and the only difference is that for a slice, the indices are int[2]'s. To make this work, I need to somehow know if any of the parameters is a slice or not. Heres what I've got so far: struct sMArray(F) { import std.traits, std.meta, core.stdc.stdlib; size_t[] dimensions; // The dimensions of the multi-array size_t[] offset; // The offset of the multi-array(for slices) void[] data; // An array of the actual element data in bytes void[] parent; // The parent array or slice, if null, no parent. this(T...)(T args) // Give the dimensions with one element(first or last) being a flag array to initialize with { static if (isArray!(T[0])) { auto dat = T[0]; dimensions = args[1..$]; enum start = 1; enum end = T.length; } else { static if (isArray!(T[$-1])) { auto dat = T[$-1]; enum start = 0; enum end = T.length-1; } else { int[] dat = null; enum start = 0; enum end = T.length; } } foreach(a; args[start..end]) dimensions ~= a; int totalSize = 1; foreach(d; dimensions) totalSize *= d; data = malloc(totalSize*F.sizeof)[0..totalSize*F.sizeof]; if (dat != null) for(int i = 0; i < dat.length; i++) (cast(typeof(dat))data)[i] = dat[i]; } ref auto opIndex() { return data; } // Index a single element, e.g., data[0, 1] or takes a slice/view of the array and returns that as another sMArray ref auto opIndex(T...)(T idx) { if (T.length != dimensions.length) assert(0, "sMArray invalid indexing!"); size_t i = 0; static foreach(k, t; T) { static if (k == 0) i = idx[0]; else i += idx[k]*dimensions[k-1]; } debug { if (i >= data.length) assert(0, "sMArray invalid index! Too Large."); } return (cast(F[])data)[i]; } // Support for `x..y` notation in slicing operator for the given dimension. size_t opSlice(size_t dim)(size_t start, size_t end) in { assert(start >= 0 && end <= this.opDollar!dim); } body { return [start, end]; } size_t GetDim(int i) { return dimensions[i]; } // Support `$` in slicing notation, e.g., arr[1..$, 0..$-1]. property size_t opDollar(size_t dim)() { return dimensions[dim]; } ~this() { free(data.ptr); } }
Sep 29 2017
On Fri, Sep 29, 2017 at 07:31:14PM +0000, Joseph via Digitalmars-d-learn wrote:I am trying to have a multi-dimensional array and opIndex has to have both an arbitrary number of parameters and allow for slicing. The problem is if I create opIndex for non-slicing, it looks like ref auto opIndex(T...)(T index) and this one catches all templates. But slices do not return the same value. I need to somehow have two opIndex's, one for slice handling and one for non-slices. Trying to accomplish arr[1..4, 5..8, 3, 3..4] I already have the single index case arr[3, 4, 3, 1] and the only difference is that for a slice, the indices are int[2]'s. To make this work, I need to somehow know if any of the parameters is a slice or not.[...] I've done this before. Basically, there are a few things you need to do: 1) Determine the type of the result. Presumably, you'll want to return the same Matrix / Array type that you started with, only with a different dimensionality, depending on how many slicing arguments you get. To do that, you need to know: a) How many of your arguments are slices b) How many are single indices. You can use a foreach loop with static if, or an analogous template, to count the number of slices. I'd go with the template solution, to ensure you don't incur unnecessary runtime overhead if you have a poor optimizer. Something like this, perhaps: template numSlices(Args...) if (Args.length > 0) { static if (Args.length == 1) { enum numSlices = is(Args[0] == int[2]) ? 1 : 0; else enum numSlices = (is(Args[0] == int[2]) ? 1 : 0) + numSlices!(Args[1 .. $]); } Then you'd construct a type of the same dimensionality as numSlices!Args as the receiving object. 2) Loop over the arguments and compute the stride information for each. (Presumably, you're constructing a slice over the original data; if you're copying the data then of course you'll do something else.) Basically something like this: foreach (arg; args) { static if (is(typeof(arg == int[2]))) { ... // compute stride corresponding to the range } else { ... // compute start offset of subdimensional slice } } T -- Don't drink and derive. Alcohol and algebra don't mix.
Sep 29 2017
On Friday, 29 September 2017 at 19:31:14 UTC, Joseph wrote:I am trying to have a multi-dimensional array and opIndex has to have both an arbitrary number of parameters and allow for slicing.You may want to look into ndslice package source code [1] --Ilya [1] https://github.com/libmir/mir-algorithm/blob/master/source/mir/ndslice/slice.d
Sep 29 2017