Copyright (C) 1997 Rich Neidinger, John Swallow, and Todd Will.  Free for
distribution to college and university instructors for personal,
non-commercial use.  If these notebooks are used in a course, the authors
request $20 per student.

20. Arrays and Matrices

Last revision:  October 1 1996

In this lab we introduce a useful new data type, called an array.  We
consider arrays, methods for their definition, initialization, and
manipulation, and pay particular attention to using iterative structures to
modify them.

Arrays

An array  is a general data type.  A one-dimensional array  is simply a
list of elements, such as "{2,3,4}"; such an array is sometimes termed a
vector.  A two-dimensional array  is a list of lists of elements, where the
lists inside all have the same length, such as "{{1,2},{4,5},{7,8}}"; such
an array is sometimes termed a matrix.   A three-dimensional array  is then
a list of lists of lists of elements, where the innermost lists all have
the same length and the lists containing those lists also all have the same
length.  For an example of a three-dimensional array, consider the
expression "{{{1,2,3},{2,3,4}},{{3,4,5},{5,6,7}},{{7,8,9},{8,9,10}}}".
Note that the innermost lists all have length 3, and the lists containing
these innermost lists all have length 2 (containing 2 lists of length 3).
Arrays can be defined for any positive integral dimension by continuing in
the same format.

Sizes

As you may surmise, arrays have a definite size.  For one-dimensional
arrays, the size is simply the number of elements.  For two-dimensional
arrays, however, the size must contain two numbers: the number of inner
lists, followed by the number of elements in each list.  For
three-dimensional arrays, the size is composed of three numbers: the number
of outermost lists, the number of lists inside a given outermost list, and
finally the number of elements in each of the innermost lists.  In the
examples above, the one-dimensional array has size 3; the two-dimensional
array has size "3,2" (or 3x2); and the three-dimensional array size "3,2,3"
(or 3x2x3).

Mathematica can determine these sizes, using a command we have considered
some time ago, "Dimensions[ ]".  Let's try it:

oneDArray = {2,3,4};
twoDArray = {{1,2},{4,5},{7,8}};
threeDArray = {{{1,2,3},{2,3,4}},{{3,4,5},{5,6,7}},
{{7,8,9},{8,9,10}}};

Dimensions[oneDArray]
Dimensions[twoDArray]
Dimensions[threeDArray]

Mathematica also has some functions which display arrays in rectangular
ways.  "ColumnForm[ ]" displays a vector as a column instead of a row, and
"MatrixForm[ ]" displays a two-dimensional array in rectangular form, where
the inner lists are written out as rows.

ColumnForm[oneDArray]

MatrixForm[twoDArray]

(For obvious reasons, there is no simple way to display an array of
dimensionality greater than two.)

Individual Elements

We can refer to individual elements of a list with array indices.  These
indices tell us how to find the element by indicating, working from the
outside in, which particular list, which element of if that list, and so
on, contains the element we are interested in. In a one-dimensional array,
we need only mention one number, the number of elements from the beginning
of the list one must count to find the element.  In a two-dimensional
array, we require two numbers:  the first index will tell us which list to
use (which "row"), the second which element of that list (which "column").
For instance, in the two-dimensional array "{{1,2,3},{4,5,6}}" the element
with value 3 has indices "1,3".  Similarly, in a three-dimensionalarray,
the three indices pinpoint, respectively, which outer list, which list of
that list, and which element of that inner list one must consider to reach
the element in question.

To determine what value resides in an array at a particular set of array
indices, we can use "Part[ ]" or its functional equivalent, "[[ ]]":

oneDArray[[2]]
Part[oneDArray,2]

twoDArray[[3,2]]
Part[twoDArray,3,2]

Note that if we specify too few indices, you will get a list or list of
lists---all of the elements of the array whose first so many indices match
our specified ones.

Part[threeDArray,2,2]

Arrays: Declaration and Initialization

To declare that a name refers to an array, we must decide from the outset
what the size of the array will be and what values we will initially assign
to its elements.  Initializing an array to be an array of zeros is
simplest, and we will begin thus.  To initialize a one-dimensional array of
zeros, we use a "Table[ ]" command.

myArray = Table[0,{10}]

For two-dimensional and three-dimensional arrays, we use multiple iterators.

my2 = Table[0,{10},{3}]

my3 = Table[0,{3},{2},{3}]

It may happen that we know some nonzero values to which we would like the
array initialized.  We can use "Table[ ]" to initialize the values of the
various entries to the results of some function or expression, which may
depend on the indices of the entries.  We might want to initialize each
entry of a 3x5 array to its own row number; in that case, we would execute

my4 = Table[i,{i,3},{j,5}]

Similarly, to initialize a 4x6 array with value "i+2^j" at indices "i,j",
we would execute

Clear[f]
f[i_,j_]:=i+2^j;
my5 = Table[f[i,j],{i,4},{j,6}]

or

my5 = Table[i+2^j,{i,4},{j,6}]

It is also possible to define special sorts of arrays in Mathematica.  An
identity matrix  is a two-dimensional array of size "n,n" which contains
all zeros, except that the elements with two identical indices have value
1.  Mathematica can create such a matrix using the "IdentityMatrix[ ]"
command.

IdentityMatrix[5]

More generally, to specify a diagonal matrix, which is a square
two-dimensional array which contains all zeros,  except that the elements
with two identical indices may have nonzero values.  Mathematica can create
such a matrix using the "DiagonalMatrix[ ]" command.

DiagonalMatrix[{1,2,3}]

Can you tell how Mathematica decides what size the diagonal matrix should be?

Arrays: Modification

We can modify the value of any array element simply by executing an
assignment statement, referring to an element via its indices.

oneDArray

oneDArray[[2]]=500

oneDArray

In many cases, though, instead of modifying a particular array's elements,
we may wish to have a copy of the old array made, and then modify the
elements in the new array.  (One particular case where we will need to do
so occurs when an array is an actual parameter in a function call; since we
cannot modify the value of a formal parameter inside a function, we must
copy the formal parameter to a local variable in order to permit
modifications.)  We can do so by using an assignment statement to complete
the first copy of matrices.

twoDArray

twoD2 = twoDArray;
twoD2[[3,2]]=0;
twoD2

Arrays are frequently manipulated using iterative structures such as "For[
]", "Do[ ]", and "Table[ ]".  Here are some examples.

Example 1: Adding to Every Element

Let's add the integer 1 to each element of a vector, by individually
assigning each entry.

addOne[v_List] := Module[{newv},
   newv = v;
   Do[
      newv[[i]] = v[[i]] + 1
   ,{i,1,Length[v]}];
   newv
]

addOne[{2,7,3,4,9}]

(Of course, this process is much simpler using Mathematica's list-based
operations.)

{2,7,3,4,9}+1

Note that we had to copy vector "v" to "newv" in order to modify it; formal
parameters such as "v" cannot be modified inside a function.

Example 2: Cumulative Sums

A more interesting example is to take a vector and find the cumulative sum
vector, made up of sums of consecutive elements beginning with the first
element, so that "cumSum[ ]" of "{2,7,3,4,9}" would return
"{2,9,12,16,25}".

Using Do and Assignment of Elements

cumSum[v_List] := Module[{sumv},
   sumv = v; (* copy the vector *)
   Do[ (* successively add vector elements *)
      sumv[[i]] = sumv[[i-1]] + v[[i]]
   ,{i,2,Length[v],1}];
   sumv (* return answer *)
]

cumSum[{2,7,3,4,9}]

Using Do and Append

Clear[cumSum];
cumSum[v_List] := Module[{sumv},
   sumv = {v[[1]]}; (* get the first entry *)
   Do[
      sumv = Append[sumv, sumv[[i-1]] + v[[i]] ]
   ,{i,2,Length[v],1}];
   sumv
]

cumSum[{2,7,3,4,9}]

One advantage of the first version of "cumSum[ ]" is that it does not use
any list functions (such as "Append[ ]") except "Length[ ]".  Many
languages support arrays but no list functions; thus, the first version is
programmable in almost any language.

Example 3: Exchanging Two Rows

Suppose we have two matrices (two-dimensional arrays) which have the same
number of columns, and we wish to exchange row "i" from the first matrix
with row "j" of the second.  Our strategy will be to copy row "i" from the
first matrix to another temporary array, to copy row "j" from the second
matrix to row "i" of the first, and finally to copy our temporary array to
row "i" of the first.  We will assume we are given two matrices as
arguments together with "i" and "j"; we will return the two new matrices in
a list.

Clear[rowSwitch]
rowSwitch[m1_,m2_,i_,j_] := Module[
                {newm1=m1,newm2=m2,temp,cols,k}, (* init newm1,2 *)
                If[Length[Dimensions[m1]]!=2||
                   Length[Dimensions[m2]]!=2,
                   Print["Arrays not 2-dimensional"],
                   If[Dimensions[m1][[2]]!=Dimensions[m2][[2]],
                      Print["Number of columns different"],
                      cols = Dimensions[m1][[2]]; (* find # cols *)
                      temp = Table[0,{cols}]; (* initialize temp *)
                      For[k=1, k<=cols, k++, (* k from 1 to cols *)
                         temp[[k]]=newm1[[i,k]]
                         ]; (* now temp has old row i *)
                      For[k=1, k<=cols, k++,
                          newm1[[i,k]] = newm2[[j,k]]
                          ]; (* now row i is old row j *)
                      For[k=1, k<=cols, k++,
                         newm2[[j,k]] = temp[[k]]
                         ]; (* now row j is temp *)
                      {newm1,newm2}
                   ]
                ]
                ];

rowSwitch[twoD2,twoDArray,2,3]

Study the code carefully.  Note again that local variables were used
instead of formal parameters so that modifications could be made.  Some
will notice that a simpler solution exists, which we give below, which uses
the fact that we can reference an entire row of a two-dimensional matrix by
specifying only the row number between double brackets.  For instance,
"newm1[[i]]" refers to the entire list of row "i" in array "newm1".

Clear[rowSwitch2]
rowSwitch2[m1_,m2_,i_,j_] := Module[
                {newm1=m1,newm2=m2,temp,cols,k}, (* init newm1,2 *)
                If[Length[Dimensions[m1]]!=2||
                   Length[Dimensions[m2]]!=2,
                   Print["Arrays not 2-dimensional"],
                   If[Dimensions[m1][[2]]!=Dimensions[m2][[2]],
                      Print["Number of columns different"],
                      temp = newm1[[i]];
                      newm1[[i]] = newm2[[j]];
                      newm2[[j]] = temp;
                      {newm1,newm2}
                   ]
                ]
                ];

rowSwitch2[twoD2,twoDArray,2,3]