Indexing matrix and manipulating shape
Getting / setting elements of matrices and changing the way of viewing matrix are required in most applications. In this article, functions for these operations are described.
Indexing
The way of specifying element(s) of a matrix is called “indexing”. Sushi2 allows most operations supported by MATLAB, except for expanding the size of matrix.
Memory layout
To use some kind of operations correctly, you need to understand memory layout of how matrix elements are stored.
Elements of a matrix are stored in the memory sequentially. In Sushi2, adjacent elements in a column are stored to adjacent memory area (called column-major). The index of a element is represented as (row, column). For example, (2, 3) element of the matrix in figure is 8. Note that the index begins with 1, not 0.
A matrix can have more than 2 dimensions. When the matrix have N dimensions, the memory order can be considered as matrices of N-1 dimensions are concatenated. In this figure, (3, 1, 2) element of the matrix is 12.
Getting / setting single element
A.get(idx1, idx2)
, where A
is a matrix and idx1, idx2
are scalar number, the expression returns the scalar number corresponding A(idx1, idx2)
.
A.set(idx1, idx2, val)
, where A
is a matrix and idx1, idx2, val
are scalar number, the expression sets the value of A(idx1, idx2)
as val
.
You can also use linear indexing, which specifies index in 1-dimensional memory layout.
In case of the matrix has more than two dimensions, the number of arguments increases corresponding to the number of dimensions.
> var A = $M.jsa2mat([[10,40,70],[20,50,80],[30,60,90]]);
undefined
> console.log(A);
Matrix 3x3 single
10 40 70
20 50 80
30 60 90
undefined
> A.get(2, 3);
80
> A.set(2, 3, 81);
undefined
> console.log(A);
Matrix 3x3 single
10 40 70
20 50 81
30 60 90
undefined
> A.get(5);
50
> A.set(5, 51);
undefined
> console.log(A);
Matrix 3x3 single
10 40 70
20 51 81
30 60 90
undefined
> var A = $M.jsa2mat([
... [[10, 20, 30],
... [40, 50, 60]],
... [[70, 80, 90],
... [100, 110, 120]]
... ]);//3D case
undefined
> A.get(1, 1, 1);
10
> A.get(1, 2, 1);
20
> A.get(2, 1, 1);
40
> A.get(1, 1, 2);
70
> A.get(2, 3);//equal to (2, 3, 1)
60
> A.get(2, 4);//assumes A reshaped into (2, 3*2)
100
> A.get(2, 3, 3);
Error: Invalid index
> A.set(10, 123);
undefined
> A.get(2, 2, 2);
123
$M.jsa2mat
works intuitively for 2D case. When the input array is more than 2 dimensions, it works as follows.
- 2Dmatrix is like [[1,2],[3,4]]
- [2Dmatrix_1, 2Dmatrix_2]
becomes 3D matrix A
, and A(i, j, 1) == 2Dmatrix_1
, A(i, j, 2) == 2Dmatrix_2
.
- [[2Dmatrix_11, 2Dmatrix_12], [2Dmatrix_21, 2Dmatrix_22]]
becomes 4D matrix A
, and A(i, j, z, w) == 2Dmatrix_wz
.
- Input can be more than 4 dimensions. Index for outer array becomes later dimension of corresponding matrix.
Getting / setting multiple elements
You can access to a subset of matrix by some ways.
First way is using colon expression.
$M.colon(start, stop)
generates a colon object, which defines a range of indices.
> var A = $M.jsa2mat([[10,40,70],[20,50,80],[30,60,90]]);
undefined
> A.get($M.colon(2,3), 3);
Matrix 2x1 single
80
90
In this case, row 2 to 3 (both edges are included) and column 3 is selected.
You can also set a matrix or scalar to the selected location.
> A.set($M.colon(2,3), 3, $M.jsa2mat([[100],[110]]));
undefined
> A
Matrix 3x3 single
10 40 70
20 50 100
30 60 110
> A.set($M.colon(2,3), 3, 123);
undefined
> A
Matrix 3x3 single
10 40 70
20 50 123
30 60 123
$M.colon()
represents whole range, and $M.colon(start, step, stop)
represents range with skipping elements or reversing.
> var A = $M.jsa2mat([[10,40,70],[20,50,80],[30,60,90]]);
undefined
> A.get($M.colon(), 2);
Matrix 3x1 single
40
50
60
> A.get($M.colon(2, $M.end), 2);
Matrix 2x1 single
50
60
> A.get($M.colon());
Matrix 1x9 single
10 20 30 40 50 60 70 80 90
> A.get($M.colon(2, 2, 8));
Matrix 1x4 single
20 40 60 80
> A.get($M.colon(8, -1, 2));
Matrix 1x7 single
80 70 60 50 40 30 20
These rule are same for matrices with more than 2 dimensions. However, user should remember that tailing dimension with size 1 is automatically removed.
> var A = $M.jsa2mat([
... [[10, 20, 30],
... [40, 50, 60]],
... [[70, 80, 90],
... [100, 110, 120]]
... ]);//3D case
> A.get($M.colon(), $M.colon(), 1);
Matrix 2x3 single
10 20 30
40 50 60
// not 2x3x1
> A.get(1, $M.colon(), $M.colon());
Matrix 1x3x2 single
10 20 30
// not 3x2
Second way is using a matrix containing linear index. A.get(B)
generates new matrix C with same size as B. C satisfies the expression C.get(i) == A.get(B(i))
.
> var A = $M.jsa2mat([[10,40,70],[20,50,80],[30,60,90]]);
undefined
> A.get(3);
30
> A.get(5);
50
> A.get($M.jsa2mat([3,5]));
Matrix 1x2 single
30 50
Third way is using a logical matrix. A.get(B)
extracts elements of A into a vector, if the value of corresponding position in B is 1.
A logical can be generated by compare functions.
> var A = $M.jsa2mat([[10,40,70],[20,50,80],[30,60,90]]);
undefined
> var B = $M.gt(A, 40);//A>40
undefined
> B
Matrix 3x3 logical
0 0 1
0 1 1
0 1 1
> A.get(B);
Matrix 5x1 single
50
60
70
80
90
Operations of matrix shape
### size
$M.size(A)
gives a row vector which contains the number of elements along each dimension of matrix A
.
$M.size(A, dim)
gives a scalar number which represents the number of elements along dimension dim
of matrix A
.
$M.sizejsa(A)
is similar to $M.size(A)
, but it returns JavaScript array.
> var A = $M.jsa2mat([[10,20,30],[40,50,60]]);
undefined
> $M.size(A);
Matrix 1x2 int32
2 3
> $M.size(A, 2);
3
> $M.sizejsa(A)
[ 2, 3 ]
numel
$M.numel(A)
gives the total number of elements of matrix A
.
> var A = $M.jsa2mat([[10,20,30],[40,50,60]]);
undefined
> $M.numel(A);
6
ndims
$M.ndims(A)
gives the number of dimensions of matrix A
.
> var A = $M.jsa2mat([[10,20,30],[40,50,60]]);
undefined
> $M.ndims(A);
2
> var A = $M.jsa2mat([
... [[10, 20, 30],
... [40, 50, 60]],
... [[70, 80, 90],
... [100, 110, 120]]
... ]);//3D case
> $M.ndims(A)
3
squeeze
$M.squeeze(A)
removes dimensions of size 1 from matrix A
and returns new matrix.
> var A = $M.rand(2, 3, 1, 4);
undefined
> $M.size(A);
Matrix 1x4 int32
2 3 1 4
> var B = $M.squeeze(A);
undefined
> $M.size(A);
Matrix 1x4 int32
2 3 1 4
> $M.size(B);
Matrix 1x3 int32
2 3 4
A.squeeze_inplace()
changes matrix A
itself.
> A.squeeze_inplace();
undefined
> $M.size(A);
Matrix 1x3 int32
2 3 4
reshape
$M.reshape(A, sz1, sz2...)
changes the shape of matrix A
. The number of elements must be same as original matrix.
One argument can be -1
, which is replaced to proper value to keep the number of elements.
The underlying memory layout is kept when reshaping.
> var A = $M.jsa2mat([[10,20,30],[40,50,60]]);
> $M.reshape(A, 1, 6);
Matrix 1x6 single
10 40 20 50 30 60
> $M.reshape(A, 6, 1);
Matrix 6x1 single
10
40
20
50
30
60
> $M.reshape(A, 3, -1);
Matrix 3x2 single
10 50
40 30
20 60
transpose
$M.transpose(A)
or $M.t(A)
transposes matrix A
. Only applied to 2-dimensional matrix.
> var A = $M.jsa2mat([[10,20,30],[40,50,60]]);
> $M.t(A)
Matrix 3x2 single
10 40
20 50
30 60
> A
Matrix 2x3 single
10 20 30
40 50 60
permute
$M.permute(A, axes)
changes the order of axes of matrix A
. This is generalized version of transpose
.
$M.ipermute(A, axes)
is inverse function of permute
.
> var A=$M.jsa2mat([[[10, 20, 30],[40, 50, 60]],[[70, 80, 90],[100, 110, 120]]]);
undefined
> $M.size(A)
Matrix 1x3 int32
2 3 2
> var B = $M.permute(A, [2, 1, 3]);// order of axes becomes [2, 1, 3] from [1, 2, 3]
undefined
> $M.size(B);
Matrix 1x3 int32
3 2 2
> A.get(2,3,1);
60
> B.get(3,2,1);
60
> var C = $M.ipermute(B, [2, 1, 3]);
undefined
> $M.size(C);
Matrix 1x3 int32
2 3 2
> $M.isequal(A, C);
true