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