Lecture 7 : Lists, Functions, Rules, and Patterns in Mathematica

Today we' ll cover how to manipulate lists, create and use functions, and use patterns and rules.  You may find patterns and rules hard to grasp at first; this is to be expected, as most programming languages don't have counterparts to these concepts . On the other hand, they are essential in Mathematica, and you'll find yourself using them surprisingly often. Learning them now will allow us to do more interesting things sooner. On that note, unless you all ask leading questions, this is about as deep into the innards of Mathematica as we'll delve. 

Don't worry about completely understanding patterns and transformation rules at this point; really this stuff only becomes clear as you start finding it necessary or convenient. For now, just try to take in the gist of the lecture, and please ask lots of questions.

Lists

Lists in Mathematica are delimited by curly braces, with elements separated by commas. They can contain any valid Mathematica expression : numbers, strings, symbols, graphics, ... 
When a list is constructed, each element is evaluated and the corresponding entry is filled with that value. 
List expressions have List as their Head.

{a,b,c,d}
List[a,b,c,d]
Length[%]

The elements in the list are referred to as its parts. To access the i-th part, use double brackets [[ ]]. When a part is a list, we say the elements of that part are one level deeper in the list. For example, the 1st level of a matrix (list of lists) consists of the rows of the matrix, and the second level consists of the entries of the matrix. Multilevel lists generalize multiply-indexed arrays. To access the i-th part of the k-th part of a list, you can use the double bracket notation again.

M = Table[(1/(i+j-1)),{i,1,5}, {j,1,5}] ;
M //MatrixForm

(* determine the depth-- number of levels-- of a list *)
ArrayDepth[M]

(* Different ways to access the 1,2 element in M *)
{M[[1]][[2]], M[[1,2]], Part[M, 1,2], Part[M, 1][[2]]}

(* Access multiple parts at the same level by using a list*)
M[[{1,2}]]
M[[1]][[{2,3}]]
M[[1]][[All]] (* All is a special directive that indicates take the full range of possible values *)

(* Accessing parts using other commands *)
First[M]
Last[M]
Take[M, 2]  // MatrixForm(* the first two rows of M *)
Take[M, -2] // MatrixForm(* the last two rows of M *)

(* Manipulating levels *)
Flatten@M (* Flattens nested lists *)

Efficient use of lists is key to Mathematica programming. For that reason, it provides many functions for manipulating lists. I'll hit the highlights here, but even the more esoteric ones can be useful.

(* List construction, in increasing generality *)
ConstantArray[RandomReal[], 5]
ConstantArray[0, {5,5}] //MatrixForm

Range[10]
Range[2,10]
Range[2,10,2]

Array[Cos,5]

M = Array[Mod,{5,5}]; (* gives an array whose (i,j)-th entry is i mod j *)
M // MatrixForm

Table[Mod[i^2,j^2], {i, 1, 5}, {j,1,5}] //MatrixForm (* this can't be done with Array because there's no function that calculates i^2 mod j^2 *)

(* List manipulation *)
Flatten[{{a},{b}, {c}}] (* flattens nested lists *)

A = B = {a,b,c}
Join[A, B] (* treats the lists as tuples *)
Union[A,B] (* treats the lists as sets *)
Union@Flatten@M (* can use Union to get reduce a list to its unique parts *)
Intersection[A,B] (* treats the lists as sets *)

Prepend[{b,c}, a] (* adds an element at the beginning *)
Append[{a,b}, c] (* adds an element at the end *)
Partition[{a,b,c,d,e,f,g}, 2]  (* splits lists into pairs *)
 
o = Range[1,10,2]
e = Range[2,10,2]
all = Riffle[o,e] (* interleave the lists *)

(* a practical application of Riffle and Partition-- this kind of manipulation is necessary because of ListPlot takes {x,y} pairs instead of lists {x}, {y} *)
x = Range[0,10] 2π/10;
y = Sin@x;
ListPlot[ Partition[Riffle[x,y],2], Joined->True]

(* Operations on lists *)
{1,2}=={3,4} (* two lists are equal iff their parts are equal *)

{1,2}/{3,4} (* arithmetic operations are elementwise *)
{1,2}+{3,4}
{1,2} {3,4}

ΠList = Prime[Range[40]];
MemberQ[ ΠList,23] (* Tests for membership in a list *)
FreeQ[ΠList, 17] (* Tests for nonmembership *)
Count[Flatten@Table[ΠList, {56}], 17] (* counts the number of occurences *)

Basic Patterns

Patterns are (in combination with its frontend) what most sets Mathematica apart from other CASes. Pattern matching is fundamental to Mathematica (in particular, functions are defined using patterns), and if you know how to use them effectively, pattern matching can save you a lot of effort. Internally, Mathematica even represents variable assignment with pattern matching!

Literal Patterns

Literal patterns can be used to manipulate expressions using the substitution operator /. and replacement rule -> operator. Mathematica compares the pattern with the parts of the expression, and replaces the parts that match.

a+b /. b->1

a+b/.{a->1,b->2}

a+b/.{{a->1, b->2}, {a->b, b->a}} (* a list of replacement rules gives list of results; note that replacement rules are done in parallel (hence we get a+b back rather than 2a or 2b) *)

D[Cos[ x], x] /. x->y
(D[Cos[x],x] /. x-> 2π x) /. x->1

Rules are themselves Mathematica expressions, and can be entered directly using the Rule head

FullForm[x->1]

y /. Rule[y, x^2]
Replace[y, Rule[y,x^2]]

Mathematica will only attempt to find direct matches; even if two expressions are mathematically equivalent, if the structures don' t line up, it will not find the pattern. This is an instance of the problem I mentioned last lecture that CASes have with determining the equivalence of two expressions. The best way to be sure that a pattern will match an expression is to compare their FullForms.

Cos[x]^2 + Sin[x]^2 /. Cos[x]^2 + Sin[x]^2 ->a (* works *)
1 /. Cos[x]^2 +Sin[x]^2 ->a (* fails *)
1 == Cos[x]^2 + Sin[x]^2 //FullSimplify (* but Mathematica realizes these expressions are equal *)

(* less obvious example *)
Cos[x]^2 +  2Cos[x]Sin[x] + Sin[x]^2 /. Cos[x]^2 + Sin[x]^2 -> a
(Cos[x] + Sin[x])^2 /. Cos[x]^2 + Sin[x]^2 -> a

Named Patterns

Literal patterns are rather restricted, because they either match exactly, or don' t. To match arbitrary expressions, use an underscore.

{ Sin[x]Cos[x]^2, Sin[y]Cos[y]^2} /. Cos[x]->1 (* here the pattern can't match the expression using a y, even though they have the same structure *)
{Sin[x]Cos[x]^2, Sin[y]Cos[y]^2, Sin[1]Cos[1]^2} /. Cos[_] -> 1 (* here anything with the correct structure will match *)

((Sin[x])^2) + ((Cos[y])^2) /. ((Cos[_])^2) + ((Sin[_])^2) ->1 (* note that multiple appearances of _ don't refer to the same expression *)

Particularly when you' re using Mathematica to perform algebraic reductions, you may want to refer back to a matched expression. To do this, use a named pattern.

{ Sin[x]Cos[x]^2, Sin[y]Cos[y]^2} /. Cos[x_]->x

((Cos[x])^2)+((Cos[x])^2)+((Sin[a])^2)+((Sin[y])^2)/. _ Cos[_]^2 + Sin[_]^2->1 (* here _ refers to 3 different expressions *)
((Cos[a])^2)+((Cos[x])^2)+((Sin[a])^2)+((Sin[y])^2)/. Cos[n_]^2 + Sin[n_]^2->1 (* a named pattern refers to only one expression *)

Multiple named patterns can be used at once

x y +c d/. a_ b_ -> a+b
x y +c d/. Times[a_, b_] -> a+b

Stored Patterns

If you have a transformation rule that you' d like to apply globally, without having to use /. on each expression, you can add it to the global rule base using an assignment.

p[x_] = x^2+3x;
h = 3 (* variables are transformation rules! *)

((Cos[p[y]])/(p[y]^2))

p[p[y]]/.y->h

Functions

Functions are useful in Mathematica even when you' re simply using it for calculations, because they allow you to avoid retyping parametrized expressions. Functions are simply parametrized expressions whose values are to be determined at a later date. To let Mathematica know what the parameters to the function are, we use patterns, and to tell it to delay evaluation of the expression, we use delayed assignment := (SetDelayed) instead of = (Set)

(* Simple function of one variable *)
sinc[x_] := Sin[x]/x;

sinc[0]
sinc[0] =1; (* note that a more specific set will override the setdelayed *)
sinc[0]
?sinc

You may wonder why we use SetDelayed instead of Set to define functions. Indeed, in some cases it makes no difference, but consider these examples:

f1[x_] = FullSimplify[x^2];
g1[x_] = Expand[x^2];

f2[x_] := FullSimplify[x^2];
g2[x_] := Expand[x^2];

f1[Cos[x]+Sin[x]]
f2[Cos[x] + Sin[x]]

g1[x+y]
g2[x+y]

The problem here is that when f1 and g1 are defined, Mathematica applies FullSimplify and Expand immediately to x^2, so these definitions are equivalent to f1[x] = g1[x] = (x^2). Using SetDelayed, Mathematica waits until x has been replaced by an expression before evaluating the calls to FullSimplify and Expand.

If a function is defined using a compound expression (grouped with parenthesis, or using Module, Block, or With), its value is that of the last expression. If the last expression has no value, the value is Null.

examplefun[x_] := ( Print["The argument was " <> ToString@x]; x^2; x^6); (* <> is the infix notation for StringJoin *)
examplefun[x] + 2

Because functions are defined with patterns, we can write functions that give you different types of output depending on the type of inputs. Only one pattern will match a particular expression, so only that function definition will apply.

RandomMat[n_] := RandomInteger[{-5,5}, {n,n}] (* random square matrix *)
RandomMat[n_, m_] := RandomInteger[{-5,5}, {n,m}] (* random rectangular matrix *)

RandomMat[4] // MatrixForm
RandomMat[4,5] // MatrixForm

(* function of 4 variables *)
distance[x1_, y1_, x2_, y2_] := (Sqrt[(((x1-x2))^2)+(((y1-y2))^2)]);

(* more elegant *)
distance[p1_, p2_] := (Sqrt[(((p1[[1]] - p2[[1]]))^2)+(((p1[[2]] - p2[[2]]))^2)]);

(* another alternative *)
distance[{x1_, y1_}, {x2_, y2_}] := (Sqrt[(((x1-x2))^2)+(((y1-y2))^2)]);

(* dimension independent, variable metric *)
distance[p1_, p2_, D_] := Norm[(p1-p2).D.(p1-p2)];

distance[{1,2,3},{4,5,6}, DiagonalMatrix[{1,2,3}]]
distance[{1,2,3},{4,5,6}]
distance[{1,2},{3,4}]
distance[1,2,3,4]

As an important aside, note that DSolve and RSolve return their solutions as lists of rules, which you can then turn into functions using what we' ve learned. Let' s revisit an example from the first lecture

DSolve[ {r''[x] + r'[x]r[x]==6, r[0]==1, r'[0]==1}, r, x] (* returns an anonymous function; more on this next lecture *)
rules = DSolve[ {r''[x] + r'[x]r[x]==6, r[0]==1, r'[0]==1}, r[x], x] (* returns a list of rules for r[x]; this is the one we want right now *)

soln[z_] := (r[x] /. rules[[1,1]]) /. x->z; (* one way to turn the result into a function *)

When determining the value of a function call, Mathematica goes with the most specific pattern match

WeirdConvert[a_ + b_] := WeirdConvert[a] + WeirdConvert[b];
WeirdConvert[a_ b_] := WeirdConvert[a] + WeirdConvert[b];
WeirdConvert[ Power[a_,p_]] := WeirdConvert[a]+p;
WeirdConvert[a_] := a;
WeirdConvert[Sqrt[a_]] := WeirdConvert[(a^p)];

WeirdConvert[(Sqrt[a b +c (d/(e+f))])]

By default, all symbols in Mathematica are global. To use symbols local to a particular function, use the Block or Module commands. Module is safest, because it creates completely new local symbols, whereas Block uses the same symbol, but gives it a local value. When in doubt, use Module. Block and Module are compound expressions, so the value of the function is that of the last statement executed (if the last statement ends in a semicolon, the value is Null)

(* example of why you might need to use local variables *)
f[g_, x_] := Integrate[g[x+s], {s,0,1}];
f[Cos, y] (* this gives the expected value *)
f[Cos,s] (* this doesn't work *)

(* possible remedies *)
f[g_, x_] := Module[{s}, Integrate[g[x+s], {s,0,1}]]; (* internally Mathematica replaces s with a new symbol of the form s$nn so there won't be any conflicts *)
f[Cos, s] (* this works *)

f[g_,x_] := Block[{s}, Integrate[g[x+s], {s,0,1}]]; (* Mathematica temporarily replaces the value of s *)
f[Cos, s] (* this doesn't work-- hence the caution to use Block when you're not clear which to use *)

(* further examples *)
Clear[x,y]
y=x
Block[{x}, x=a; x+y] (* Mathematica replaces x with x$nn *)
x
Module[{x}, x = a; x +y] (* Mathematica replaces the value of x locally *)
x