This formalizes a bit an indexing calculus with brackets which incorporates array construction, array comprehension, slicing and mapping of arrays. Note: ...
is the splatting operator and …
is etcetera.
1.[…]
or T[…]
is a shape preserving array construction operator (T
a type)
2.f[…]
respective A[…]
is a map
or array indexing operator (A
an array, f
a function)
Both follow the same fundamental rule
shape([expr]) == shape(expr)
and
shape(A[expr]) == shape(expr)
For f[A]
the shape of the result is determined by shape(f[A]) == (shape(f(A[1]...)...,shape(A)...)
similar as in mapslices.
Indexing and construction differ mainly with respect to the meaning of ,
as delimiter.
Within bracket expressions ;
, ...
, :
are operations constructing shaped objects as detailed below, almost agreeing with julias meaning.
For arrays A
,
shape(A) == size(A).
Further,
shape(A[a,b,…]) == (shape(a)...,shape(b)...,…)
so A[1,1]
will be a singleton with shape(1,1)= ()
An array A
provides a singleton indexing method A[i,j, …]
in this gist written with parenthesis A(i,j, …)
for clarity and is not much different from a function f(i,j,…)
, except that is has shape(A) = size(A)
.
Array construction is done with commas
[A,B,…]
optionally by giving the type of the array elements
T[A,B,…]
Taking the shape preserving principle strictly would entail A == [A]
, so only [A,]
would denote the array containing A
, but of course we
define
[A] = [A,]
as exception to the shape preserving principle.
The concatenating delimiter ;
has formally the property
shape([A1; A2; ; An ]) = (r[1] + r[2] + ... + r[n], s, t)
if shape(Ai) = (r[i], s, t)
. This has nice consequences later when introducing matrix literals.
Especially for A = [a; b; c]
, M=[m; n; o]
[A;M] = [a; b; c; m; o]
and
[A;] = A
Comprehensions
[expr(i) for i in I]
[expr(i,j) for i in I, j in J]
T[expr(i) for i in I]
T[expr(i,j) for i in I, j in J]
f[expr(i) for i in I]
f[expr(i,j) for i in I, j in J]
A[expr(i) for i in I]
A[expr(i,j) for i in I, j in J]
are all defined by the properties
[expr(i) for i in I][k] = expr(I[k])]
[expr(i,j) for i in I, j in J] == expr(I[k],J[l])
f[expr(i) for i in I][k] == f(expr(I[k]))
f[expr(i,j) for i in I, j in J][k,l] = f(expr(I[k],J[l]))
A[expr(i,j) for i in I, j in J][k,l] = A[expr(I[k],J[l])]
etc.
It is natural to extend the meaning of ;
to comprehensions
[expr(i); for i in I][k] == [expr(I[1]); … ; expr(I[n])]
For arrays A
and indices i,j
A[i,j] == A[(i,j)...] == A((i,j)...) == A(i,j)
shape(A[i,j]) == shape((i,j)...) == (,)
In general
A[I] == A[idx for idx in I] == [A[idx] for idx in I] == [A(idx...) for idx in I]
The current convention A[(i,j)] == A[i,j]
implies the last equality.
This obeys shape(A[I]) == shape(I)
.
As tuples are opened, for an array of tuples I
A[I] = [A[ids...] for ids in I
Shaped objects separated by commas in indexing correspond to forming of a product with A[I,J][ids] == A[I,J][ids..] == A(ids...)
for tuple ids
.
A[I,J] == A[idx for idx in product(I,J)] == A[(i...,j...) for i in I, j in J]
The colon can be formally seen as 1:size(A,i)
with shape (size(A,i),)
.
Hence
A[:] == A[i for i in 1:size(A,1)] = A[(i,)... for i in 1:size(A,1)]
A[:,:] == A[(a,i,c) for i in 1:size(A,1), j in 1:size(A,2)]
A[a,:,c] == A[(a,i,c) for i in 1:size(A,2)]
etc.
The same formula hold for functions f
f[I,J] == f[idx for idx in product(I,J)] == f[(i...,j...) for i in I, j in J]
The interpretation of tuples as elements from the array case is extended according to f[(i,j)] == f(i,j)
Array construction with [A,B,C]
and T[A,B,C]
is not covered by this product calculus with comma as delimiter.
It turns out that derived from the julia definition of [v w]
we obtain a concept related to the diagonal of the
product A[I,J]
. A good name for the concept would be splicing, it is almost the same as the zip operation.
We define
A[J K][i] == A(J[i]...,K[i]...)
A[J K][i,j] == A(J[i,j]...,K[i,j]...)
etc. and obtain especially for vectors
[v w z][i,:] = [v[i], w[i], z[i]]
One can think of v w
as vector of tuples without parentheses, because
shape([v w]) == (shape(v)..., 2)
and shape shape(A[v w]) == shape(v)
or as vector of tuples taking into account that []
opens tuples.
Note that both space separation and ;
-separation can be extended conformal with the array concatenation notation
[A1 B1 C1; A2 B2 C2; A3 B2 C3]
if [A B] == [a b v w x]
for A == [a b]
and B == [v w]
from the vector of tuple without parentheses interpretation.
Also, a notation for the diagonal emerges
A[: :] = diag(A)
If A
and B
have shape, the only sane convention is
that [f(A,B)]
is the array containing f(A,B)
but we might set
[expr(:,:) over [A B]][i,j] = expr(A[i,j], B[i,j])
[expr(:,:) over [A,B]][i,j,k,l] = expr(A[i,j], B[k,l])
and obtain a nice array map notation. [Should tuples be automatically opened here expr(A[i,j]..., B[i,j]...)
?]
Whether for an d
-dimensional array A = A[:, :, …, :]
the expression
A[i, …, k]
is understood as A[i, …, k, :, …, :]
as in C or as in Julia and Matlab or as A[:, …, :,i, …, k,]
seems to be independent of this
calculus.
Edit: I clarified a bit how arrays of tuples behave as indices and that [ ]
opens tuples, but the question how arrays of tuples are injected into functions and array indicies will have to be revisited.