• Aucun résultat trouvé

; funxi fun xi ;

5.3 Flow control

Conditional functions

In this section we will look at functions that control the flow of execution of an evaluation.

Perhaps the simplest and easiest to understand of these class of functions is theIf state-ment. Here is a rather simplistic implementation of the absolute value function, using If.

In[1]:= abs x_ : If x 0, x, x

In[2]:= abs 4

Out[2]= 4

The If function takes three arguments: the first is a test; if the test evaluates to True, then the second argument is evaluated; if the test evaluates toFalse, then the third argument of theIf is evaluated.

If can also be used in conjunction with the higher-order functions discussed in Chapter 4 to achieve greater flexibility. For example,abs can now be mapped over a list of numbers.

In[3]:= Map abs, 2, 1, 0, 1, 2

Out[3]= 2, 1, 0, 1, 2

By default, this function will notautomatically map across lists.

In[4]:= abs 2, 1, 0, 1, 2

Out[4]= If 2, 1, 0, 1, 2 0, 2, 1, 0, 1, 2 , 2, 1, 0, 1, 2

If you want abs to behave like many of the built-in functions and automatically map across lists when they are given as the argument toabs, you need to make the function Listable as described in Sections 2.4 and 4.2.

In[5]:= SetAttributes abs, Listable

In[6]:= abs 2, 1, 0, 1, 2

Out[6]= 2, 1, 0, 1, 2

Here are some additional examples using If. Given a list, the following function adds 1 to all thenumeric quantities occurring in it.

In[7]:= incrementNumbers lis_ : Map If NumericQ #1 , # 1, # &, lis

In[8]:= incrementNumbers 4, f, 6.1 I,

Out[8]= 5, f, 7.1 , 1

Here is a function that divides 100 by every number in a numerical list, except 0s.

In[9]:= divide100By lis_ : Map If # 0, #,

Here is a function to remove consecutive occurrences of the same value.

In[11]:= removeRepetitions lis_ :

Fold If #2 Last #1 , #1, Append #1, #2 &, First lis , Rest lis

In[12]:= removeRepetitions 0, 1, 1, 2, 2, 2, 1, 1

Out[12]= 0, 1, 2, 1

As a final example ofIf, the functionapplyChar takes a list as an argument. This list must contain, first, a character, which must be one of "+", "-", "*", or"/"; that character must be followed by all numbers. applyChar applies the function named by the character to the elements of the rest of the list.

In[13]:= applyChar lis_ : Module op First lis , nums Rest lis ,

If op " ", Apply Plus, nums , If op " ", Apply Subtract, nums ,

If op " ", Apply Times, nums , If op " ", Apply Divide, nums ,

Print "Bad argument to applyChar"

In[14]:= applyChar " ", 1, 2, 3, 4

Out[14]= 10

(Recall theModule function, which permits us to introduce local variables. In this case, it saves us from having to writeFirst[lis] and Rest[lis] several times each.)

Even though the argument list in applyCharmust contain one of the four operators as its first element, it is still best to check for it explicitly; otherwise, if the condition is ever violated, the results may be very mysterious. We have used the Print function, which prints all of its arguments (of which it can have an arbitrary number) and then skips to a new line.

In[15]:= applyChar "^", 2, 5, 10 Bad argument to applyChar

Notice that what we have in this code is several nested Ifs, each occurring in the false part of the previous one. Thus, the structure of the computation is a sequence of tests of predicates until one is found to be true, at which point a result can be computed. Such a sequence of cascading If statements can get quite long, and the indentation can become unmanageable, so it is conventional to violate the usual rule for indenting If expressions and indent this type of structure as follows:

If cond1, result1, If cond2, result2, If condn, resultn,

resultn1] …]]

Conditional definitions can be written using another construct in Mathematica, the Condition operator, /;. For example, the abs function can be entered (using several definitions) as follows:

In[16]:= Clear abs

In[17]:= abs x_ : x ; x 0

In[18]:= abs x_ : x ; x 0

The first definition should be interpreted as “abs[x] is equal tox whenever (or under the condition that)x is greater than or equal to 0” and the second definition as “abs[x] is equal to the opposite of x wheneverx is less than 0.”

The conditions on the right-hand side of the rules can, in fact, be entered on the left-hand side of these definitions as follows:

In[19]:= abs x_ ; x 0 : x

In[20]:= abs x_ ; x 0 : x

This last notation has the advantage of preventing the right-hand side of our definitions from being evaluated whenever the pattern on the left does not match.

In[21]:= abs 4

Out[21]= 4

In[22]:= abs z

Out[22]= abs z

This use of multiple rules associated with the symbol abs is a very useful and powerful means of associating rules with symbols under user-defined conditions and we turn to it next.

Multiclause definitions

Theabs function defined above is fine for integers and real number arguments, but, since the complex numbers cannot be ordered, the initial test comparing a complex number argument with 0 will fail.

In[23]:= abs 3 4 I

GreaterEqual::nord :

Invalid comparison with 3 4 attempted. More…

Less::nord : Invalid comparison with 3 4 attempted. More…

Out[23]= abs 3 4

We can solve this problem by providing an additional definition forabs.

In[24]:= Clear abs ;

abs x_ : Sqrt Re x 2 Im x 2 ; x Complexes;

abs x_ : x ; x 0 abs x_ : x ; x 0

The test as the first argument ofIf on the right-hand side checks to see ifx is an element of the domain of complex numbers and, if it is, then rex 2 imx2 is com-puted. Ifx is not complex, nothing is done, but then the other definition forabs will be invoked.

In[28]:= abs 3 4 I

Out[28]= 5

In[29]:= abs 3

Out[29]= 3

The condition itself can appear on the left-hand side of the function definition, as part of the pattern match. Here is a slight variation on theabs definition.

In[30]:= Clear abs

abs x_ : If x 0, x, x

abs x_ ; x Complexes : Sqrt Re x 2 Im x 2

In[33]:= abs 3 4 I

Out[33]= 5

In[34]:= abs 3

Out[34]= 3

We may want to add an additional rule for symbols.

In[35]:= abs x_ ; Head x Symbol : x

In[36]:= abs z

Out[36]= z

Such a definition is called amulticlause definition. In this case we have associated three rules withabs; two are rather specific and will only be applied if the argument to abs passes the conditions specified. If neither of those conditions are met, then the most general rule (the one with no conditions on x) will be used.

Which and Switch

Recall the earlier definition of applyChar defined using cascadingIfs.

In[37]:= applyChar lis_ : Module op First lis , nums Rest lis ,

If op " ", Apply Plus, nums , If op " ", Apply Subtract, nums ,

If op " ", Apply Times, nums , If op " ", Apply Divide, nums ,

Print "Bad argument to applyChar"

Needless to say, this is a little difficult to read and figure out which clause goes with which If. Fortunately, cascaded Ifs are so common that Mathematica provides a more direct way of writing them, using the function Which.

Which cond1, result1, result of theWhich expression itself. If none of the conditions turns out to be true, then it will test the final “condition,” namely the expressionTrue, which always evaluates to true, and it will then returnresultn 1.

applyChar can now be written more neatly.

In[38]:= applyChar lis_ : Module op First lis , nums Rest lis ,

Which op " ", Apply Plus, nums , op " ", Apply Subtract, nums , op " ", Apply Times, nums , op " ", Apply Divide, nums ,

True, Print "Bad argument to applyChar"

One last form deserves mention. Our use of the Which command is still quite special, in that it consists of a simple sequence of comparisons between a variable and a constant. Since this is also a common form,Mathematica again provides a special function for it, called Switch.

Switch[expr, pattern1, result1, pattern2, result2, patternn, resultn, _, resultn 1

]

This evaluates expr and then checks each pattern, in order, to see whether expr matches; as soon asexpr matches one, saypatterni, it returns the value of resulti. Of course, if none of the patterns pattern1, …,patternn matches, the_ certainly will.

If all the patterns happen to be constants, theSwitch expression is equivalent to the following Which expression.

Which[expr == pattern1, result1, expr == pattern2, result2, expr == patternn, resultn, True, resultn1

]

Here, then, is our final version of applyChar.

In[39]:= applyChar lis_ : Module op First lis , nums Rest lis ,

Switch op,

" ", Apply Plus, nums ,

" ", Apply Subtract, nums ,

" ", Apply Times, nums ,

" ", Apply Divide, nums ,

_, Print "Bad argument to ApplyChar"

Notice thatSwitch uses the blank character,_, for the final, ordefault case, just as Which uses the always-true expression True. We will have much more to say about patterns and pattern matching in Chapter 6.

Piecewise

Several of the functions we created in previous sections could be caste as piecewise-defined functions. Although technically not a procedural construct,Piecewise (new in Version 5.1) is designed specifically for such problems. The syntax is Piecewise e1,c1, …, en,cn which outputse1 ifc1 is true,e2 ifc2 is true, … ,en ifcn is true, and 0 otherwise (the default).

So, for example, here is the definition for the absolute value function given as a piecewise object.

In[40]:= abspw x_ : Piecewise x, x 0 , x, x 0

Piecewise objects display as you would expect in traditional mathematical notation.

In[41]:= abspw x

Out[41]= x x 0

x x 0

Furthermore,Piecewise is fully integrated with the algebraic, symbolic, and graphical functions inMathematica and so is preferable to other approaches.

In[42]:= Integrate abspw x , x, 1, 1

Notice that the definition of the absolute value function given in terms of condition-als is not fully supported by many of the built-in functions.

In[45]:= Clear abs

In[46]:= abs x_ : x ; x 0

In[47]:= abs x_ : x ; x 0

Often, when we write functions, we know ahead of time that the definitions we give them are valid only for certain kinds of inputs. For example, the following definition for the factorial function only makes sense for positive integers.

In[50]:= fact 0 1;

fact n_ : n fact n 1

In[52]:= fact 5

Out[52]= 120

If we were to givefact an argument that was not a positive integer, the recursion could run away from us.

In[53]:= fact 3.4

$RecursionLimit::reclim :

Recursion depth of 256 exceeded. More…

Out[53]= 2.729671867921455 10494Hold fact 250.6 1

Conditionals are a convenient way of checking that the arguments to our functions pass some criteria. For example, there are several ways that we could make the fact function valid only under the condition that its argument is a positive integer. Here is how we might approach it using theIf construct to test that n passes the appropriate criteria.

In[54]:= Clear fact

In[55]:= fact 0 1;

In[56]:= fact n_ : If IntegerQ n && n 0, n fact n 1

In[57]:= fact 5 , fact 3 , fact 2.4

Out[57]= 120, Null, Null

We see that the function works fine for positive integers, but since we did not give an alternative condition to theIf function, nothing is returned (technicallyNull is returned) when the test condition fails.

Let us define a message that will be output in the case that the argument to fact fails the positive integer test.

In[58]:= fact::noint "Argument `1` is not a positive integer.";

We then useMessage as the third argument to ourIf, so that when the condition fails the message will be triggered. Essentially Message messname, e1, e2, … prints using StringForm messg, e1, e2, … , where messg is the value of the message name and theei are substituted in for any expressions of the form`i`. In the above example, the message name isnoint and its value is the string beginning with "Argument...". In this example, the value of n will be substituted into the string where the`1` occurs.

In[59]:= fact n_ : If IntegerQ n && n 0, n fact n 1 ,

Message fact::noint, n

In[60]:= fact 3

fact::noint : Argument 3 isnot a positive integer.

Of course, there are a variety of ways of using conditionals to do argument checking.

Here are three more implementations, without the messaging.

In[61]:= fact1 0 1;

Summary

When writing a function whose result must be computed differently, depending upon the values of its arguments, you have a choice:

1. Use a multiclause definition, where the conditions are optional, and may appear after the right-hand sides.

f pattern1_ /; cond1 := rhs1

f patternn_ /; condn := rhsn

2. Use a single-clause definition with a conditional expression.

f[x_] := If cond1, rhs1, If condn, rhsn, rhsn 1 ]

In the latter case, ifn is greater than two, use the equivalentWhich expression; and if all conditions have the formx == consti, for a given variablex and some constantsconsti, use the Switch function.

The next section contains several applications that use various combinations of the procedural constructs we have learned in this chapter.

Exercises

1. Write the function signum[x] which, when applied to an integerx, returns 1, 0, or 1, according as x is less than, equal to, or greater than, 0. Write it in three ways:

using three clauses, using a single clause withIf, and using a single clause with Which.

2. Extendsignum from Exercise 1 to apply to both integers and reals; again, write it in three ways (though you may use more than three clauses for the multiclause version).

3. Write applyChar in multiclause form, using pattern matching on the first element of its argument.

4. UseIf in conjunction withMap or Fold to define the following functions:

a. In a list of numbers, double all the positive numbers, but leave the negative numbers alone.

b. remove3Repetitions is likeremoveRepetitions except that it only alters three or more consecutive occurrences, changing them to two occurrences; if there are only two occurrences to begin with, they are left alone. For example, remove3Repetitions[{0,1,1,2,2,2,1}] will return{0,1,1,2,2,1}.

c. Add the elements of a list in consecutive order, but never let the sum go below 0.

In[1]:= positiveSum 5, 3, 13, 7, 3, 2

Out[1]= 6

Since the 13 caused the sum to go below 0, it was instead put back to 0 and the summation continued from there.

5. Using NestWhileList, write a function CollatzSequence[n] that produces the Collatz sequence for any positive integern. The Collatz sequence is generated as follows: starting with a numbern, if it is even, then output n2; ifn is odd, then output 3n 1. Iterate this process whilen 1.

5.4 Examples

Sieve of Eratosthenes

One of the oldest algorithms in the history of computing is the Sieve of Eratosthenes.

Named after the famous Greek astronomer Eratosthenes (ca. 276 – ca. 194 BC), this method is used to find all prime numbers below a given number n. The great feature of this algorithm is that it finds prime numbers without doing any divisions – an operation that took considerable skill and concentration before the introduction of the Arabic numeral system. In fact, its only operations are addition and component assignment.

The algorithm can be summarized as follows: to find all the prime numbers less than an integern:

• create a list of the integers 1 through n

• starting withp 2, cross out all multiples of p

• incrementp (that is, add 1 top) and cross out all multiples of p

• repeat the previous two steps until p n.

You should convince yourself that the numbers that are left after all the crossings out are in fact the primes less than n. This algorithm lends itself very well to a procedural approach, so let us walk through the steps.

We will use a For structure for this problem. The syntax is For[start,test,incr, body], wherestart will first be evaluated (initializing values), and thenincr andbody will be repeatedly evaluated until test fails.

1. Let lis be a list containing all the integers between 1 and n.

In[1]:= n 20;

lis Range n

Out[2]= 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,

11, 12, 13, 14, 15, 16, 17, 18, 19, 20 2. Let p 2. Repeat the following two steps:

• Starting at position 2p, “cross out” everypth value inlis. We will assign 1 to lis at positions 2p, 3p, and the 1 will represent a crossed out value.

In[3]:= p 2;

Do lis i 1, i, 2 p , n, p

In[5]:= lis

Out[5]= 1, 2, 3, 1, 5, 1, 7, 1, 9, 1, 11, 1, 13, 1, 15, 1, 17, 1, 19, 1

• Whilep n, incrementp by 1, until lis[[p]] is not 1, or until p n 1.

In[6]:= n 20;

lis Range n ; For p 2,

p 1 && p Floor Sqrt n , p ,

Do lis i 1, i, 2 p , n, p

3. The non-1s inlis are all the prime numbers less than or equal ton.

In[9]:= DeleteCases lis, 1

Out[9]= 2, 3, 5, 7, 11, 13, 17, 19

Let us put these steps together in our function Sieve.

In[10]:= Clear n, p, lis

In[11]:= Sieve n_Integer : Module lis Range n , p ,

For p 2,

p 1 && p Floor Sqrt n , p ,

Do lis i 1, i, 2 p , n, p ; DeleteCases lis, 1

Here are a few simple tests to check the correctness of our function. First we check that Sieve produces the correct number of primes less than a large integer.

In[12]:= Length Sieve 105

Out[12]= 9592

The built-in PrimePi[x] gives the number of primes x less than or equal tox.

In[13]:= PrimePi 105

Out[13]= 9592

Next we do some simple timing tests to check the efficiency of this algorithm against the built-in functions that are optimized for this task.

In[14]:= Sieve 106 ; Timing

Out[14]= 13.62 Second, Null

In[15]:= Timing Table Prime i , i, 106 ;

Out[15]= 5.648 Second, Null

In[16]:= Timing Map Prime, Range 106 ;

Out[16]= 5.628 Second, Null

For numbers in this range (less than about 106), sieving is fairly efficient – its speed is within an order of magnitude of the built-in algorithms. But, beyond this range, it does tend to bog down and it would be best to consider specialized algorithms that are asymptoti cally fast (for large integers, PrimePi uses an algorithm due to Lagarias, Miller, and Odlyzko that is based on estimates of the density of primes).

Classifying points

Quadrants in the Euclidean plane are conventionally numbered counterclockwise from quadrant 1 (x andy positive) to quadrant 4 (x positive,y negative). The functionpoint Loc[{x,y}] will compute the classification of point x, y, according to Table 5.1.

Point Classification

We will use this problem to illustrate the features covered in this chapter, by giving a number of different solutions, using multiclause function definitions with predicates, single-clause definitions withIf and its relatives, and combinations of the two.

Perhaps the first solution that suggests itself is one that uses a clause for each of the cases above.

It is a good idea to include the last condition as a comment, rather than as a condi-tion in the code, becauseMathematica would not realize that the condition has to be true at that point and would check it anyway.

We will use the following list of points as our test cases.

In[24]:= pts

0, 0 , 4, 0 , 0, 1.3 , 2, 4 , 2, 4 , 2, 4 , 2, 4 ;

In[25]:= Map pointLoc, pts

Out[25]= 0, 1, 2, 1, 2, 3, 4

Translated directly to a one-clause definition usingIf, this becomes:

Actually, a more likely solution here uses Which.

In[28]:= pointLoc x_, y_ : Which

x 0 && y 0, 0, suppose the argument is ( 5, 9). It will require five comparisons of 5 with 0 and three comparisons of 9 with 0 to obtain this result. Specifically:

1. evaluate x == 0; since it is false, the associated y == 0 will not be evaluated, and we next

2. evaluate y == 0 on the following line; since it is false, 3. evaluate x == 0 on the third line; since it is false,

4. evaluate x > 0 on next line; since it is false, the associated y > 0 will not be evalu-ated, and we next,

5. evaluate x < 0 on the next line; since it is true, we do,

6. the y > 0 comparison, which is false, so we next,

7. evaluate x < 0 on the next line; since it is true, we then evaluatey < 0, which is also true, so we return the answer3.

How can we improve this? By nesting conditional expressions inside other condi-tional expressions. In particular, as soon as we discover thatx is less than, greater than, or equal to 0, we should make maximum use of that fact without rechecking it. That is what the followingpointLoc function does. false, we next, (ii) evaluatex > 0; since it is false, we go to the third branch of theWhich, evaluate True, which is, of course, true; then, (iii) evaluate y < 0, which is true, and we return 3. Thus, we made only three comparisons – a substantial improvement.

When pattern matching is used, as in our first, multiclause solution, efficiency calculations are more difficult. It would be inaccurate to say that Mathematica has to comparex andy to 0 to tell whether the first clause applies; what actually happens is more complex. What is true, however, is that it will do the comparisons indicated in the last four

When pattern matching is used, as in our first, multiclause solution, efficiency calculations are more difficult. It would be inaccurate to say that Mathematica has to comparex andy to 0 to tell whether the first clause applies; what actually happens is more complex. What is true, however, is that it will do the comparisons indicated in the last four