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.

12. Structured Patterns

Last revision:  November 15 1996

While some programming languages allow functions which have parameters of
complex types, few do so with as much generality as Mathematica.  In this
section we consider some of the extensive ways in which Mathematica allows
arguments to be recognized in patterns, as well as some examples of how
useful such pattern recognition can be.  We also consider optional and
default parameters.

Advanced Patterns

Structured Patterns with Conditions

In the previous section we considered data type checking, boolean tests,
conditions, and simple structured patterns, each of which is incorporated
in the following example.

Clear[below]
below[{x_Integer,y_?NumberQ} /; x>y]  :=
   "The point is below y=x."

below[{345234,23}]

below[{1,12}]

With regard to conditions, it is important to realize that conditions can
be applied only to a single preceding parameter, as in the example above,
where the single parameter was a list with two entries.  Conditions cannot
be successfully reference the values of any parameters before or after the
immediately preceding parameter.  Thus, the following function does not
perform correctly.

Clear[below]
below[x_Integer,y_Integer /; x>y] :=
                        "The point is below y=x."

below[{345234,23}]

below[{1,12}]

In this section we consider a new sort of pattern matching capability:
allowing a parameter to represent several values, and allowing a parameter
to represent several or no values, depending on the input to the function.
We then consider examples of advanced patterns.

Multipart Arguments (Two, Three Underscores, MatchQ)

Recall that we defined a function "switchTwo[ ]", which switched the first
two elements of a three-element list.  What if we wanted to define
"switchTwo[ ]" similarly for a list of any number of elements greater than
two? Here is a cumbersome solution:

Clear[switchTwo];
switchTwo[list_List /; Length[list]>1 ] :=
                        Join[{list[[2]],list[[1]]},Drop[list,{1,2}]]

switchTwo[{1,2,3,4,5,6}]

Why cumbersome? It is so because the argument is followed by both a data
type check and a condition, and the overall definition is much too long for
such a simple operation.  Another solution might use a Module and copy the
list to another one, altering specific elements in the new list:

Clear[switchTwo];
switchTwo[list_List /; Length[list]>1 ] :=
                        Module[{newlist},
                                  newlist = list;
                                  newlist[[2]]=list[[1]];
                                  newlist[[1]]=list[[2]];
                                  newlist
                        ];

switchTwo[{1,2,3,4,5,6}]

Still, this is a long solution.  Instead, we use an appropriate pattern.
If two underscores are placed after a parameter name in a function
definition, then Mathematica will allow more than one value to take its
place in the argument passed to the function.  For instance, if we define
the function as follows, the "rest of the list" beyond the first two
elements will be left intact.

Clear[switchTwo];
switchTwo[{a_, b_, c__}] := {b, a, c}

switchTwo[{1,2,3,4,5,6}]

Notice that the parameter "a" was assigned the integer 1, the parameter "b"
the integer 2, and the parameter "c" the sequence "3, 4, 5, 6".  The catch
here is that "c" is required to have some value; with our current
condition, we cannot call "switchTwo[ ]" with only two parameters, because
then "c" would be left without a value.

switchTwo[{1,2}]

If we wish to permit the assignment of no values to the parameter "c", we
append to "c"three underscores; this syntax indicates to Mathematica that
the argument "c" can take no value, if necessary.

Clear[switchTwo];
switchTwo[{a_, b_, c___}] := {b, a, c}

switchTwo[{1,2,3,4,5,6}]

switchTwo[{1,2}]

switchTwo[{1}]

MatchQ

It is not always obvious whether or not a certain actual parameter matches
a pattern required for a function.  Using "MatchQ[ ]" helps us solve this
dilemma:  it performs the pattern-matching for the first argument, given
the pattern in the second, and returns a boolean value indicating whether
or not the match was successful.

MatchQ[1,t__]

MatchQ[{1,2},{a_,b__,c___}]

MatchQ[{1,2},{a_,b__,c__}]

Using Patterns

An Average Function

Our goal here is to define a general-purpose average function.  We might
begin by first defining a function that finds the average of a list.

Clear[ave];
ave[x_] := N[ Apply[ Plus, x ] / Length[x] ]

We must be prepared, however, for some bad input to give us problems.  For
instance:

ave["Hello"]

ave[1,2,3]

ave[{1,2,3}]

We therefore improve the function with our usual additions:

Clear[ave];

ave::usage = "ave[list] finds a numeric approximation to the average of the
elements of the list";

ave[x_List] := N[ Apply[ Plus, x ] / Length[x] ];

ave[x_] :=
   Print["Your argument \"",x,"\" is not a list."];

ave["Hello"]

ave[1,2,3]

ave[{1,2,3}]

Looking at the action of our function, we notice that we might want "ave[
]" to average a sequence of numbers, as in the second case above, even if
the numbers are not initially in the form of a list.  We can define "ave[
]" to take a list of more than one argument and enclose it in a list, as
follows:

Clear[ave];

ave::usage = "ave[list] finds an real number approximation to the average
of the elements of the list";

ave[x_List] := N[ Apply[ Plus, x ] / Length[x] ];

ave[x___] := ave[{x}];

ave[x_] :=
   Print["Your argument \"",x,"\" is not a list."]

ave["Hello"]

ave[1,2,3]

ave[{1,2,3}]

Now we have a function which can average several arguments.  What happened
with "Hello", though?   It matched "x___" and not "x_List", so
"ave["Hello"]" became "ave[{"Hello"}]", and then it was averaged!  Note
that the definition "ave[x_] := ..." never executes, since all arguments
will be encapsulated in a list or be a list already.  Here is a better
solution:

Clear[ave];

ave::usage = "ave[list] finds an real number approximation to the average
of the elements of the list";

ave[x_List] := N[ Apply[ Plus, x ] / Length[x] ];

ave[x___?NumberQ] := ave[{x}];

ave[x_] := Print["Your argument \"",x,"\" is not a list."];

ave["Hello"]

ave[1,2,3]

ave[{1,2,3}]

Further improvements are certainly possible:  we have not yet prevented the
user from explicitly giving "ave[ ]" a list of non-numeric values.
Consider how we might improve the function to enforce that constraint.

Counting Random Numbers (Count)

In this example we make use of the Mathematica function "Count[ ]", which
determines how many elements of a list satisfy a certain condition.

?Count

The pattern in the second parameter need not include underscores, data
types, or boolean tests; we are permitted to ask for exact matches, by
setting the second parameter to the expression we wish matched exactly.
This behavior is analogous to defining a function with parameters without
underscores, such as "f[x]=3":  in this case, only when the parameter is
exactly "x" will the definition be used.  We might want to know how many
zeros appear in a given list.

Count[{1,2,3,0,2,0,4},0]

We might instead ask how many numeric approximations occur in a given list.

Count[{1.2, 1, 3.7, dog }, _Real ]

Note here that "_Real" is a legitimate pattern; we have not included a
parameter name, for we do not have a need to reference the value being
tested.  In the following example, in which we use a data type check and a
condition to test both whether the value is a numeric approximation and
whether it is less than 3, we need to refer to the value, so we give it a
name:

Count[{1.2, 1, 3.7, dog }, x_Real /; x<3 ]

Using "Count[ ]", we can now simulate coin tosses, asking how many of 100
random numbers, chosen from 0 to 1, are less than 0.5.

Count[ Table[ Random[],{100}], x_ /; x<.5]

Default Arguments

A default argument is a parameter which, if not specified, is assigned some
particular default value.  In the function "N[ ]", for instance, the second
parameter is optional, and if it is not specified, the number of digits for
the approximation is determined internally by Mathematica.  Another example
is provided by the "RotateLeft[ ]" function.  This function rotates the
list given in the first argument left by the number of positions given in
the second argument.

?RotateLeft

But note that if we omit the second argument, the list will be rotated by
one position.  The second argument is thus assigned the "default value" 1;
if that argument is left out, then "1" is the value it will take.

In order to specify a default parameter inside a function definition, we
place a colon (":") followed by the default value after the parameter
specification, before the next comma, if applicable.  As an example,
suppose we want a function "addOnes[ ]" which prepends and appends as many
1s as are indicated by the second argument to the list indicated by the
first argument.  We might define "addOnes[ ]" as follows:

Clear[addOnes];
addOnes[l_List,i_Integer:1]:=
                Nest[(Prepend[Append[#,1],1])&,l,i];

addOnes[{2,2},5]

addOnes[{2,2}]

It is true that there is an alternate method of specifying the same
function "addOnes[ ]"; we could write that if there are two arguments, it
should have the definition above, while if it has only one argument, it
should have an alternate definition, using 1 instead of "i":

addOnes[l_List,i_Integer]:=
                Nest[(Prepend[Append[#,1],1])&,l,i];
addOnes[l_List]:=
                Nest[(Prepend[Append[#,1],1])&,l,1];

Notice that this method eliminates the default argument.  This method is
less attractive, however, because, first, a reader must look closely at
each function to notice that the functions are basically the same, and,
second, Mathematica is required to store two complicated definitions
instead of one, wasting memory.

Finally, we should say that we have not covered all of Mathematica's great
generality in specifying patterns.  Consult Stephen Wolfram's Mathematica,
section 2.3, for more information.