opSlice() or opIndex() for the entire slice?
Ali Çehreli via Digitalmars-d-learn
digitalmars-d-learn at puremagic.com
Sun Sep 7 13:25:05 PDT 2014
I think I figured this one out.
Before 2.066, we did not have proper support for multi-dimensional
slicing. The following were the semantics we had:
-- The opIndex() overloads provided access to direct elements. Since
multi-dimensional support was incomplete, opIndex() was about accessing
a single object:
a[i] // element i by opIndex(size_t)
a[i, j] // element i,j by opIndex(size_t, size_t)
-- The two overloads of opSlice() returned range objects that
represented either all of the elements or a slice of them:
a[] // opSlice()
a[i..j] // opSlice(size_t, size_t)
Note that those features are still usable but according to
documentation, they are discouraged.
Since 2.066, we have multi-dimensional slicing. The way we look at these
operators have changed a little:
-- The opIndex() overloads now return ranges that represent a range of
elements. For example, if 'a' is a two-dimensional matrix, the first
line below should return a sub-matrix inside it, not a single element:
a[i..j, k..l] // opIndex(MyRange, MyRange)
The following can indeed return a single element but I think it is a
valid design decision to return a sub-matrix consisting of a single
element as well:
a[i, j] // a matrix of one element by opIndex(size_t size_t)
A single matrix row:
a[i, j..j] // opIndex(size_t, MyRange)
Here is the answer to my original question: Consistent with the above,
now opIndex() must take the responsibility of returning all of the elements:
a[] // opIndex()
-- With that change of responsibility, what remains for opSlice() is the
only task of producing a range object that opIndex() can later use to
represent one or more elements:
a[i..j, k..l] // opIndex(opSlice(i, j), opSlice(k, l))
In summary, the following is what opSlice() should do almost in all cases:
Tuple!(size_t size_t) opSlice(size_t beg, size_t end)
{
return tuple(beg, end);
}
Also note that representing i..j and k..l can always be done by a
Tuple!(size_t, size_t) without loss of any information. (i.e. MyRange
above can actually be Tuple!(size_t, size_t)):
I am attaching an example that helped me understand what was going on.
Note that the program is decidedly "elegant" as opposed to "efficient".
:) For example, even initializing a single element by random access goes
through these steps:
Initializing 1 of 80
deneme.Matrix.opIndexAssign!(int, int).opIndexAssign
deneme.Matrix.opIndex!(int, int).opIndex
deneme.Matrix.opDollar!0LU.opDollar
deneme.Matrix.opDollar!1LU.opDollar
deneme.Matrix.subMatrix
deneme.Matrix.this
deneme.Matrix.opAssign
[...]
But I like it. :)
Ali
import std.stdio;
import std.format;
import std.string;
struct Matrix
{
private:
int[][] rows;
/* Represents a range of rows of columns. */
struct Range
{
size_t beg;
size_t end;
}
/* Returns a reference to a sub-matrix that correspond to
* the range arguments. */
Matrix subMatrix(Range rowRange, Range columnRange)
{
writeln(__FUNCTION__);
int[][] slices;
foreach (row; rows[rowRange.beg .. rowRange.end]) {
slices ~= row[columnRange.beg .. columnRange.end];
}
return Matrix(slices);
}
public:
this(size_t height, size_t width)
{
writeln(__FUNCTION__);
rows = new int[][](height, width);
}
this(int[][] rows)
{
writeln(__FUNCTION__);
this.rows = rows;
}
void toString(void delegate(const(char)[]) sink) const
{
formattedWrite(sink, "%(%(%5s %)\n%)", rows);
}
/* Assigns the value to all of the elements of the
* matrix. */
Matrix opAssign(int value)
{
writeln(__FUNCTION__);
foreach (row; rows) {
row[] = value;
}
return this;
}
/* Applies the operation to each element and assigns the
* result back to it. e.g. 'm += 42'*/
Matrix opOpAssign(string op)(int value)
{
writeln(__FUNCTION__);
foreach (row; rows) {
mixin ("row[] " ~ op ~ "= value;");
}
return this;
}
/* Returns the size of the provided dimension. */
size_t opDollar(size_t dimension)() const
{
writeln(__FUNCTION__);
static if (dimension == 0) {
return rows.length;
} else static if (dimension == 1) {
return rows.length ? rows[0].length : 0;
} else {
static assert(false,
format("Invalid dimension: %s",
dimension));
}
}
/* Returns a range representing the provided indices. */
Range opSlice(size_t dimension)(size_t beg, size_t end)
{
writeln(__FUNCTION__);
return Range(beg, end);
}
/* Returns a sub-matrix corresponding to the arguments. */
Matrix opIndex(A...)(A args)
{
writeln(__FUNCTION__);
Range[2] ranges = [ Range(0, opDollar!0),
Range(0, opDollar!1) ];
foreach (i, a; args) {
static if (is (typeof(a) == Range)) {
ranges[i] = a;
} else static if (is (typeof(a) : size_t)) {
ranges[i] = Range(a, a + 1);
} else {
static assert(false,
format("Invalid index: %s",
typeof(a).stringof));
}
}
return subMatrix(ranges[0], ranges[1]);
}
/* Assigns the value to each element of the sub-matrix
* determined by the arguments. */
Matrix opIndexAssign(A...)(int value, A args)
{
writeln(__FUNCTION__);
Matrix subMatrix = opIndex(args);
return subMatrix = value;
}
/* Applies the operation to each element of the
* sub-matrix. e.g. 'm[i, j] += 42'*/
Matrix opIndexOpAssign(string op, A...)(int value, A args)
{
writeln(__FUNCTION__);
Matrix subMatrix = opIndex(args);
mixin ("return subMatrix " ~ op ~ "= value;");
}
}
/* Applies the provided code and prints the result as well as
* the new state of the matrix. */
void apply(string code)(Matrix m)
{
writefln("\n--- %s ---", code);
mixin ("auto result = (" ~ code ~ ");");
writefln("result:\n%s", result);
writefln("m:\n%s", m);
}
void main()
{
enum height = 10;
enum width = 8;
auto m = Matrix(height, width);
int counter = 0;
foreach (column; 0 .. height) {
foreach (row; 0 .. width) {
writefln("Initializing %s of %s",
counter + 1, height * width);
m[column, row] = counter;
++counter;
}
}
writeln(m);
apply!("m[1, 1] = 42")(m);
apply!("m[0, 1 .. $] = 43")(m);
apply!("m[0 .. $, 3] = 44")(m);
apply!("m[$-4 .. $-1, $-4 .. $-1] = 7")(m);
apply!("m[1, 1] *= 2")(m);
apply!("m[0, 1 .. $] *= 4")(m);
apply!("m[0 .. $, 0] *= 10")(m);
apply!("m[$-4 .. $-2, $-4 .. $-2] -= 666")(m);
apply!("m[1, 1]")(m);
apply!("m[2, 0 .. $]")(m);
apply!("m[0 .. $, 2]")(m);
apply!("m[0 .. $ / 2, 0 .. $ / 2]")(m);
apply!("++m[1..3, 1..3]")(m);
apply!("--m[2..5, 2..5]")(m);
apply!("m[]")(m);
apply!("m[] = 20")(m);
apply!("m[] /= 4")(m);
apply!("(m[] += 5) /= 10")(m);
}
More information about the Digitalmars-d-learn
mailing list