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