# [PYTHON MATRIX-SIG] Assorted remarks

Graham Hughes graham@fishnet.net
Tue, 19 Sep 1995 17:31:31 -0700

```Another compendium of responses to various messages.

--- Guido van Rossum <guido@CNRI.Reston.VA.US> ---

>- Allowing M[i, j] for (multidimensional) sequence types would also
>meaning that D[i, j] would be equivalent to D[(i, j)] for
>dictionaries.

Sounds good to me. Alternatively, we could interpret D[i,j] as D[i][j]...
After all, basically that's what M[i,j] is. Might warrant consideration.

>- Should M[i][j] still be equivalent to M[i, j]?

Probably. While slicing would be difficult as your earlier remarks point
out, it seems that allowing this will allow the use of sequences of
sequences. Actually, I'm not entirely convinced that M[i:j][k] is *not*
impossible; give me a week or so to decide on this, and I may come up with
something that doesn't require any changing of the core language...

>- Now we have multidimensional sequence types, should be have a
>multidimensional equivalent of len()?  Some ideas:

Actually, I feel that for a multidimensional sequence, len() should behave
the same way it always had. Allowing for greater dimensions will give
greater flexibility, yes, but it may also break `older' functions that
assume that what's being passed to them is a 2D array. This is mainly
because len() is defined to return an integer, sadly, and not one of our
magic sequences; if it did return the magic sequence, we would be able to
totally ignore how many dimensions we have.

>  - len(a, i) would return a's length in dimension i; len(a, i) == len(a)

As to extending len(), I don't think it's even necessary. Just do len(a).
Short, simple, requires nothing out of the ordinary, *and* will even return
a 1 or something like that *if* [] returns an array of the proper rank (a 3D
will return a rank 2 array, 2 will return 1, etc.). This will not in fact
cause problems, because all operations on arrays should be defined to be
'elementwise' by default. This means we can pass a 3D array to something
expecting a matrix, and it won't make a difference. APL/J ranking does this
automatically, and given a little time I can probably work up an example
matrix that will do that too.

>  - dim(a) (or rank(a)?) would return the number of dimensions

If we have shape(), that's not terribly useful either. Just do len(shape(a)).

>  - shape(a) would return a tuple giving a's dimensions, e.g. for a
>  3x4 matrix it would return (3, 4), and for a one-dimensional
>  sequence such as a string or list, it would return a singleton
>  tuple: (len(a),).

Why a tuple, and not an array of rank 1? The array can be manipulated more
effectively than the tuple...

>- How about multidimensional map(), filter(), reduce()?

Not needed, really; see following remarks.

>  - map() seems easy (except there seems to be no easy way to specify
>  the rank of the operator): it returns a similarly shaped
>  multidimensional object whose elements are the function results for
>  the corresponding elements of the input matrix

If we use APL/J ranks, this is mostly taken care of automagically. The
function will request what it wants from the matrix, and the matrix can do
whatever it needs to with that.

Ah, but let's suppose you want the dimensions in a different order; let's
suppose you've got [ [ [1,2], [2,3] ] , [ [3,4], [4,5] ] ] (2x2x2 array).
Normally, map would invert over [ [1,2], [2,3] ] and [ [3,4], [4,5] ]. But
suppose you want it to behave differently; you want to 'rotate' the matrix.
I argue that this is an important enough operation that it should be
divorced from map, reduce, and filter, and brought into its own (notably
unlike APL, BTW; APL had a special modifier on *every* sequence operation to
do this. That is inefficient :).

>  - filter() is problematic since the columns won't be of the same
>  length

filter() should work just like map(); i.e. just like it already does. The
function that filter calls should know exactly what it wants out of the
array passed; there's no reason for filter() to have this information.

>  - reduce()??? -- someone who knows APL tell me what it should mean

Oh, ok; this follows right along the lines of the above stuff; it should
work like it always did. Reducing a 2d array will simply have a grouping of
1d arrays being passed to the function. Since the function knows what it
wants (sense a common theme here?), the overall effect will be transparent.

I really *need* to get that sample matrix written up to illustrate this...

>- Multidimensional for loops?  Or should for iterate over the first
>dimension only?

Same as above, i.e. first dimension only. If you want a different dimension,
rotate() the matrix.

BTW, notice that if (very strong if) I can get slicing to work properly,
*none* of this would require altering the language. Even if I can't, they
shouldn't require any alterations beyond the original [i,j] change.

Secret bonus; we can use [i,j] to signify non-continguous slices. While you
said you're not fond of this earlier (not quoted), and I agree that the
example given was a bit strange, this can give a great deal of additional
power to the matrix class. More on this later.

--- forrest@rose.rsoc.rockwell.com (Dave Forrest) ---

>This is a strong reason to provide two different interfaces - matrix
>and array.  You can implement them both with the same code, but just
>provide a simple interface to matrix that restricts itself to two
>dimensions and does matrix multiplication with operator *.  Array then
>acts "element-wise" and generalizes to any dimension,etc.

Not really. I'll reiterate what I've said before, that you don't need it
because all your functions will work fine with whatever dimension thingy we
pass it, with a bumper; *if* we do two different things, and you discover
that your original code needs to be modified somehow, or someone gets
confused and passes mixed arguments (matrix to array, or vice versa), all of
a sudden everything gets thoroughly confused. This is not a problem in
standard Python, since you can't add different types, but it might crop up
here, particularly if we write most of the interface in Python (which is
entirely possible, and possibly desirable).

Basically, my main argument against splitting it up is that it adds unneeded
complexity for a relatively small bonus (just write a matrix_multiply()
function that expects rank 2 arrays, and everything works peachy), and that
it would segregate the two entirely too much. For example, I would have no
way of taking my array and doing parallel matrix multiplications on it (i.e.
multiplying three sets of matrices at the same time), because while I can do
the parallelism in 'array' class and the matrix multiplication in the
'matrix' class, I would have no way to fold them together. As a result, I
would likely write a matrix_multiply() for the general matrix, and
_entirely_ sidestep the submatrix class. If you ever need to multiply
several matricies together (and you probably do) this parallelism is much
more efficient (because you spend more time in C code multiplying than in
Python figuring out which one to do next) than swapping matrices around.
Examine your code; you probably do something like (dropping into C for a while)

matrix m;
int i;

for (i = 0; i < 4; i++)

matrix_multiply(m,m);
matrix_multiply(m,m);

Notice that you're doing two multiplications. In C, there is no reason to do
parallelism (for efficiency sake) because you're not kicking back up into an
interpreter every time you multiply something. The parallel matrix would
work exactly the same way (would look a little different, I think, but not a
real problem), but would only kick in the interpreter *once*. This, for 5,
10, 200 matrices is a *real* timesaver. This point is why APL is so fast,
even though it is interpreted; it spends most of its time in C, not an
interpreter.

---

Graham