5.4. Conditional statements
In the previous two sections of this
chapter we learned how to make Maple repeat a block of statements. In this
section we learn how to make Maple skip over certain blocks of statements.
Here is a simple procedure. It takes
in two numbers as parameters and it returns the larger of the two numbers.
> 
if
a >= b then a else b fi; 
Try it out.
The procedure bigger
introduces another important element of programming,
the conditional statement (also
called a ifthenelsefi statement
) . The conditional statement allows Maple to make a
choice when it is computing. A conditional statement is also sometimes called
a branching statement since it gives Maple a choice between two possible branches
of calculations to make. Here is Maple's syntax for a conditional statement.
The part of the ifthenelsefi statement
between the if and
the then is
called the conditionalpart and
it is either true or false. If the conditionalpart is true, then Maple executes
the statements between the then
and the else
. These statements are called the body
of the thenpart . If the conditionalpart
is false, then the Maple executes the statements between the else
and the fi
. These statements are called the body
of the elsepart . The bodies of either the
then or else part can contain any number of Maple commands. The body
of the elsepart is optional and if it is left off, then the conditional statement
has the following simpler form, which is referred to as an ifstatement
.
If the conditionalpart of an ifstatement
is true, then Maple executes the body of the thenpart. If the conditionalpart
of an ifstatement is false, then Maple does not execute any statements, and
it appears as if the ifstatement did nothing.
The formatting of the conditional statement,
with the bodies of the then and else parts indented slightly and the words
else and
fi on
their own lines, is not part of the syntax. But the formatting makes it a
lot easier to read a conditional statement and should be used most of the
time. For some practical tips on how to work with multiple lines in a conditional
statement, see the last section of the previous chapter.
Here are a few examples of simple conditional
statements. The next command randomly generates a zero or one and if the random
number is zero, the statement outputs heads
, otherwise it outputs tails
. Try executing this statement several times.
> 
if rand(0..1)()
= 0 then 
If we remove the elsepart of the ifthenelse
statement, so that it becomes an ifstatement, then the statement will produce
no output about half of the time. Try executing this statement several times.
> 
if rand(0..1)()
= 0 then heads fi; 
The following execution group generates
a long list of random integers between 1 and 10 and then it uses an ifstatement
inside of a forinloop to determine what percentage of the random integers
were 10's. What answer do you expect to get?
> 
seq( rand(1..10)(),
i=1..N ): 
> 
for i in % do #
Check for 10's. 
> 
if
i = 10 then counter := counter+1 fi 
Try executing the execution group several
times. Try changing the value of N
to 100 or 10 or 10000.
In the last section of this chapter we
saw that loop statements can be very useful commands to use at the Maple command
prompt. The conditional statement however is of pretty limited use at the
Maple prompt. Instead it is almost always used in the body of a procedure
definition or in the body of a loop. Almost all of our examples of conditional
statements will be in procedure bodies.
One common use of conditional statements
in mathematics textbooks is in the definition of piecewise defined functions,
that is, functions defined by different formulas on different parts of the
domain. Here are a few examples.
Suppose we wanted to represent in Maple
the mathematical function defined by
for
and by
for
. Here is how we can do it using a procedure containing
a conditional statement.
> 
if
x < 0 then x^2+1 else sin(Pi*x) fi 
Notice how there is only one boolean
expression, even though there are two pieces to the function. The second piece
of the function, the
part, should apply whenever
. But we only have
whenever the boolean expression x<0
is false, which automatically puts us in the elsepart
of the conditional statement. So only one boolean expression is needed for
a piecewise defined function with two pieces. But this is not how traditional
mathematics books would typeset the definition of this function. Mathematics
books almost always write out a boolean expression for each piece of the function,
like this.
What appears in a mathematics book is
more like the following Maple procedure.
> 
if
x < 0 then x^2+1 else 
> 
if
x >= 0 then sin(Pi*x) fi; 
This version has two boolean expressions
because it has two ifstatements. This is not the preferred way of defining
our function in Maple for several reasons. First of all, it is not as easy
to read as the previous version. With only one conditional statement we know
that the elsepart is mutually exclusive of the thenpart. With two conditional
statements it is not obvious that the two statements are mutually exclusive.
A reader must carefully examine the boolean expressions to determine if they
are meant to be mutually exclusive or not. Secondly, the version with one
conditional is computationally more efficient than the version with two conditional
statements. Every call to the second version must evaluate two boolean expressions
while every call to the first version only needs to evaluate one boolean expression.
This can make a difference if the function is going to be called thousands
(or millions or even billions ) of times.
Exercise :
Define a function g similar
to the last definition of f in
such a way that the two boolean expressions are not mutually exclusive. Draw
a graph of g .
If the boolean expressions are not mutually exclusive, how does that affect
the graph of g ?
Let us graph our function f
to see what it looks like.
> 
plot( f, 1..1,
discont=true, color=red ); 
It is worth noting that the following
command does not work,
even though it looks perfectly OK. The function f
is converted to an expression by evaluating it
at x , and
then the form of the plot command
for expressions is used.
Error, (in f) cannot determine if this expression is true or false: x < 0
What went wrong is that Maple is using
its rule of full evaluation, so Maple tries to evaluate all of the operands
in the plot command
before actually
calling the plot procedure.
When Maple tries to evaluate f(x)
there is an error, since the actual parameter
x is
an unassigned variable and the conditional statement in f
has no way to determine if the boolean expression
x<0 is
true or not.
Error, (in f) cannot determine if this expression is true or false: x < 0
The following command does work, since
it prevents the evaluation of f(x)
until the plot
procedure actually starts sticking numbers as
actual parameters into the formal parameter x
in f(x)
.
> 
plot( 'f(x)', x=1..1
); 
In an optional section later in this
chapter we will say more about this situation with f(x)
.
Exercise :
Here is what seems to be a reasonable definition for the function
f .
> 
if
x < 0 then x^2+1 fi; 
> 
if
x >= 0 then sin(Pi*x) fi; 
But it is not correct. Look at the graph
of this version of f .
What happened to the left half of
f , the part for
x<0 ?
Here is a hint. What is the return value for the procedure call f(2)
?
Exercise :
Consider the following two functions.
> 
f := proc(x) if
x>=0 then x^2 else 0 fi end; 
> 
g := proc(x) if
x>=0 then x^2 fi end; 
Their graphs look similar.
But they are not the same function. Explain
how they differ.
Here is an interesting variation on the
idea of a piecewise defined function. We create a randomly defined piecewise
function. The following procedure, like the piecewise defined function above,
evaluates f by
choosing between two expressions, x^2+1
and sin(Pi*x)
. But instead of basing the choice of the expression
on the value of the input x ,
this version bases the choice on a random number generated within the procedure.
> 
if
rand(0..1)()=0 then x^2+1 else sin(Pi*x) fi 
So the value of the function
f at any point
will be one of two randomly chosen numbers. Try executing the following command
several times.
> 
f(5), f(5), f(5),
f(5), f(5); 
Here is what a graph of this function
might look like. Notice that each time the function is graphed, we get a different
graph (why?).
Exercise :
What causes the vertical bands of red in the graph? Here is a hint.
> 
plot( f, 1..1,
style=point, symbol=circle, numpoints=50, adaptive=false );

Suppose that we want to represent in
Maple a piecewise defined function with three pieces. For example, suppose
we want to represent the function g defined by
for
, by
for 0 <
<
, and by
for
,
or, to use a notation similar to (but
not exactly like) standard mathematical notation,
Here is a procedure that computes this
function.
> 
x^26*x*Pi+9*Pi^2x+3*Pi

This procedure uses a conditional statement
as the body of the elsepart of another conditional statement. We call these
nested conditional statements
. Notice three things. First, notice how three levels
of indentation are used to help show the structure of the procedure body,
in particular, the way the second conditional statement is the elsepart of
the first conditional statement. Second, there are only two boolean expressions
even though there are three pieces of the function. The third piece of the
function acts as the "default" piece and it applies whenever the first two
pieces do not. Third, notice how the second boolean expression does not say
0<x<3*Pi (as
it would probably be written in a mathematics book). There are two reasons
for not writing the boolean expression this way. First, it is syntactically
incorrect in Maple (see the next section of this chapter for more about the
syntax of boolean expressions). Second, it is partially redundant with the
first boolean expression. Since we are in the elsepart of the first (outer)
conditional statement, we know that x<=0
is false, so it must be that x
is positive, so there is no need to have the boolean
expression check again if 0<x
.
Let us plot this function.
Exercise :
Why was the right hand endpoint of this plot set to 3*Pi+3
? If we make the left hand endpoint 4
, what would be a good choice for the right hand endpoint?
What would the graph of g look
like if we graphed it over a large domain, say from 100 to 100? Why?
Nested conditional statements are fairly
common, so Maple has a special abbreviation for them. Here is the definition
of g using
this abbreviation.
> 
x^26*x*Pi+9*Pi^2x+3*Pi

This version of g
has only one
conditional statement in it (notice the single
fi at
the end of the procedure body and the use of fewer levels of indentation).
There is no nesting of conditional statements in this version of g
. Where the previous version of g
had a conditional statement in the elsepart of
the outer ifthenelsefi statement, the abbreviated version has an elifpart
( elif is
an abbreviation for " el se
if ") followed
by an elsepart. This form of the conditional statement is called an
ifthenelifthenelsefi statement.
There can be as many elifthen
clauses as you want in an ifthenelifthenelsefi
statement. For example, if we want to represent a piecewise defined function
with four pieces in its definition, then we can use two elifthen clauses
(in a single ifthenelifthenelse statement).
When you read a conditional statement
like this, it is important to remember that the boolean expressions are "cumulative".
So for example, the x^2 part
is not used just when x<=2
is true as the elif
clause just before it might seem to imply. The
x^2 part
is only used when x<=2 is
true and
x<=1 is
false. The test for x<=2 only
comes after the test for x<=1
fails. Similarly, the test for x<=3
only comes after both x<=1
fails and x<=2
fails. And the x^26*x+12
part is used only if each of the three tests
x<=1 ,
x<=2 , and
x<=3 all
fail.
Let us look at graphs of these last two
functions.
The syntax for a ifthenelifthenelsefi
statement should be pretty clear by now.
Remember that there can be as many elifthen
clauses as needed in this form of the conditional statement.
The next example shows how we might need
two ifthenelsefi statements nested inside of an ifthenelsefi statement,
one in each of the then and else parts. This procedure finds the largest of
three numbers.
> 
bigger3 := proc(
a, b, c ) 
> 
if
a >= c then a else b fi; 
> 
if
b >= c then b else c fi; 
Try this procedure out.
This procedure could use the
elif abbreviation
for one of the nested conditional statements, but that probably would not
make the procedure any easier to understand.
Exercise :
Rewrite the procedure bigger3
using an elifclause in the outer conditional
statement.
There is another way to implement
bigger3 . This version
uses nested calls to our procedure bigger
instead of nested conditional statements. (Recall
that bigger was
defined at the very beginning of this section.)
> 
bigger3 := proc(a,
b, c) 
> 
bigger(
bigger(a, b), c ); 
This is a common technique in programming.
Use one procedure as part of the definition of another procedure. If
we compare the two versions of bigger3
, notice how the inner call to bigger
plays the same role as the outer conditional statement,
and the outer call to bigger plays
the role of both the
inner conditional statements.
We can also create a procedure
bigger4 that
finds the largest of four numbers. We can do this two ways, one way using
nested conditional statements and another way using nested procedure calls
to bigger3 and/or
bigger .
The nested conditional statement version of bigger4
will be quite messy but you should try writing
it. Here are several ways of writing bigger4
using nested procedure calls.
> 
bigger4 := proc(a,
b, c, d) 
> 
bigger(
bigger(a, b), bigger(c, d) ); 
> 
bigger4 := proc(a,
b, c, d) 
> 
bigger(
bigger( bigger(a, b), c ), d); 
> 
bigger4 := proc(a,
b, c, d) 
> 
bigger(
bigger3(a, b, c), d ); 
There are still several other ways of
making bigger4 out
of bigger and
bigger3 .
What are they?
Exercise :
Write a version of bigger4 that
uses only nested conditional statements.
Exercise :
Write a version of bigger4 that
uses procedure calls nested inside of conditional statements.
For the sake of completeness, what about
finding the maximum of an arbitrary number of numbers. This is like our problem
of finding the average of an arbitrary number of numbers. We solve it by using
a list (which is a data structure) as the input to our procedure.
> 
biggest := proc(
list::list(numeric) ) # Type check the input. 
> 
candidate
:= list[1]; # Make an initial guess. 
> 
for
i from 2 to nops(list) do 
> 
if
list[i] > candidate then 
> 
candidate
:= list[i]; # Update our guess. 
> 
candidate;
# Return our final answer. 
Now try it out.
> 
biggest( [1,2,3,4,5,100,6]
); 
Let us test our procedure on a randomly
generated list of integers.
> 
random_list_of_integers
:= [ seq( rand(), i=1..12 ) ]; 
> 
biggest( random_list_of_integers
); 
Exercise :
Change the definition of biggest
to use a call to procedure bigger
in place of the ifstatement.
Exercise :
If you read the optional section on the special local variables args
and nargs
, then write a version of biggest
that does not need the brackets in its procedure
call.