M-5: Structured Programming

In this lesson and the next one we introduce the basic concepts of structured programming: loops, conditionals, and (in the next lesson) procedures. We will not go as slow as basic introductory material (see CS Circles for such a level of explanation but in Python), but we’ll give examples as we go along.

Our lessons try to give a short but comprehensive summary of the most important tools that a Maple programmer needs to know how to use. But you can always find out more information from the Maple programming guide which gives a more narrative description than the ? help pages.

We’ve already seen the seq() command, which lets you define a sequence of items by iterating a base expression many times. For example, seq(x^2, x=1..5) gives the sequence 1, 4, 9, 16, 25. However, sometimes what you want to repeat is more complicated than a simple expression like x^2. An excellent example is, evaluate the first 10 partial sums of the power series

\(\exp(x) = 1 + x + x^2/2! + x^3/3! + \cdots\)

at \(x=0.6\). In principle we can do this using nested seqs but let’s try doing it in a more natural way.

What we want to do can be explained in plain English:

  1. Keep a running sum, initialized to zero.
  2. Repeat the following steps, starting at k=0 and repeating with k=1, 2, … until k=9:
    • Add \(0.6^k/k!\) to the running sum
    • The value of the running sum is the kth term in the series

This is how we’d write it in Maple.

> runningSum := 0;
> for k from 0 to 9 do
     runningSum := runningSum + 0.6^k/k!;
     "The ", k, "th term is ", runningSum;
  end;

At this point, we want you to try writing it yourself. To do this, press Shift-Enter instead of just Enter to finish the second, third, and fourth lines. That is to say,

> runningSum := 0;                         # press ENTER
> for k from 0 to 9 do                     # press SHIFT-ENTER
     runningSum := runningSum + 0.6^k/k!;  # press SHIFT-ENTER
     "The ", k, "th term is ", runningSum; # press SHIFT-ENTER
  end;                                     # press ENTER

If you got this to work, you’ll see a bunch of blue output (see below). But to hit possible problems off at the head, we will discuss the formatting in more detail. The reason for that you needed to press SHIFT-ENTER is that the whole for loop (from for… to … end) should lie within a single Maple “execution group.” Execution groups are the [-shaped things on the left-hand side of the screen.

Because this is a little tricky, we show here an example of correctly inputting the for loop versus doing it incorrectly. Here’s what a correct version looks like:

 

Here is another version that runs. It works, but it is functionally a little different because you are forced to always initialize the running sum and evaluate the for loop since they’re in the same execution group. As well, there are multiple command prompts (>) in a single execution group, which has no substantive effect but makes it harder to read.

Finally, here’s a version that will not run.

If you end up with such a version, try selecting all the lines and using Edit > Split or Join > Join Execution Groups (or F4).

Back to the actual code. If you followed the original instructions, your output will look like this:

Awesome! More than just this basic example, let us now give the general syntax and structure of a for loop. Caveat: the spaces at the front of each line are not required (specifically, e.g., the three spaces before runningSum := runningSum + 1), they are totally ignored by Maple. However, it’s always a good idea to use this kind of indentation to make your code clearer for yourself and others who might read it. Likewise, in our examples, the interior of a nested loop will be indented by 3+3=6 spaces, a triply-nested loop by 9 spaces, etc.

A general version of the for loop is:

for «loopVariable» from «minValue» to «maxValue» do
  «body» # possibly consisting of several lines
end;

This tells Maple precisely to set the loop variable to «minValue» and then to execute the body, then to set the loop variable to «minValue»+1 and to execute all steps in the body again, etc, with the last loop being the one where the loop variable has value «maxValue». Take a minute to see how this generic syntax compares with the example above, and how the outputs correspond to the exact lines that Maple executes (and the order in which they are executed).

It is very important to follow the syntax including do and end or else Maple won’t be able to understand you. Sometimes, you might want the loop variable to be decreasing, or to go up in steps of size greater than 1: to do this, look up the syntax in ?for.

An instructive question: how many times will the body be executed in such a for loop? Answer: (maxValue – minValue + 1). Forgetting the +1 can be a common source or errors.

When it comes to loops, Maple’s rules for ; versus : are a bit different: if the loop is terminated with end: then no output will be printed, and if it is terminated with end; then all outputs will be printed. It does not matter whether the lines in the body of the loop end with ; or :.

Printing Nicely

The paragraph above makes it unclear how we can execute the output of our for loop in a nicer format: we want to hide the first line in the body, and show the second. Additionally, the second line’s output is pretty ugly. To get around this, we are going to use the formatted print command  printf().

The way that we will use printf is by replacing the line

"The ", k, "th term is ", runningSum;

with

printf("The %ath term is %a\n", k, runningSum);

The first argument to printf is a template (or format string). In this example, the template says that we want a string that looks like The Xth term is X but we want the first X replaced with the value of k, and we want the second X replaced with the value of runningSum. The %a is a generic format specifier; if you are interested in the different ways that you can format output, check out ?printf. The \n is needed to end a line (see what happens if you leave it out).

Finally, if

  1. we modify the line above to use printf as indicated
  2. we change the end; terminating the loop to  end:

we’ll get a program that shows us this output:

There are other variations possible depending on your goal. We could replace the printf line with just

print("The", k, "th term is", runningSum);

and you’ll see that it gives the right information, but is harder to read. Another advantage of learning printf is that it’s just one letter away from fprintf, which lets you write output to text files.

While Loops

The for loop is great when you have a predefined start and end value for your variable. However, it’s sometimes the case that you don’t know your destination until you get there.

As a concrete example, let’s say that we want to follow the example above, but instead of specifying the number of terms in advance we just want however many terms are necessary until we have a value that is accurate to \(\pm10^{-20}\). Consider the following code:

> Digits := 30;
  runningSum := 0: 
  k := 0:
  while abs(runningSum - exp(0.6)) >= 1E-20 do
     runningSum := runningSum + 0.6^k/k!;
     printf("The %ath term is %a\n", k, runningSum);
     k := k + 1;
  end:
  printf("The exact value: %a\n", exp(0.6));

We use a few simple new features:

  • Digits, the global variable controlling floating-point accuracy
  • 1E-20, scientific notation for \(1 \times 10^{-20}\)
  • abs, the absolute value function

That having been said, how does a while loop operate? Its general form is

while «test» do
  «body»         # possibly multiple lines
end

Here, test should be some boolean (true/false) expression. When Maple comes to execute the loop, it checks the test. If the test is true, then the body is executed. But once the end is reached, it runs again: the test is checked again, and the body is executed again. Only once the value of «test» is false does the loop end. Afterwards, Maple keeps computing the following lines. In the example above, this yields

Again, compare the generic form with the specific example and see that Maple does the same thing you would do if you evaluated these commands by hand.

Infinite Loops

An inevitable consequence of working with while loops is that you will sometimes make your computer enter an infinite loop. For example, if you forgot to write k := k + 1; in the above example, then since a while loop (unlike a for loop) doesn’t automatically increment any counters, it would run forever. Try it! — but be prepared to stop it. The way to stop an infinite loop in Maple is by pressing the red stop sign button with a hand on it: , at least in the latest version (at least on Windows). As far as we know, there’s no keyboard shortcut for this.

Conditionals: The if statement

Sometimes you want to execute a collection of lines or not, depending on some particular conditions. Keeping with the theme of our examples above, let’s move from the exponential power series to the power series of the sine function,

\displaystyle\sin(x) = x - x^3/3! + x^5/5! - \cdots = \sum_{k \ge 0, k \textrm{ odd} } (-1)^{(k-1)/2}x^k/k!

What would it take to modify the most recent example (approximating within \(10^{-20}\)) to work with the sine function? The main difficulty is that we only want the term to be added to the power series when k is odd. For this, we use the following code:

> Digits := 30;
  runningSum := 0: 
  k := 0:
  while abs(runningSum - sin(0.6)) >= 1E-20 do
     if k mod 2 = 1 then  
        runningSum := runningSum + (-1)^((k-1)/2)*0.6^k/k!;
     end: 
     printf("The %ath term is %a\n", k, runningSum);
     k := k + 1;
  end:
  printf("The exact value: %a\n", sin(0.6));

Note that we are nesting an if statement inside of a while loop: there is no limit to the nesting that is allowed. Also, note that this is our first use of the mod operator.

The general format of an if statement is:

if «test» then
  «body»         # possibly multiple lines
end

It’s worth yet again comparing the generic example with the specific one given above. Note in particular that because of how we structured our code, the printf statement and increment-k statement are executed regardless of whether k mod 2 = 1 was true. The output is shown below:

else and elif

Finally, we would like to put one last finishing touch on the program. We should replace 1th with 1st, etc, making it use proper English. This is an excellent opportunity to use an else statement. As the name suggests,

if «test» then
  «body»           # possibly multiple lines
else
  «alternate body» # possibly multiple lines
end

means that we execute the body when the test is true, and the alternate body when the test is false. An extremely common situation is that we want to chain together several if-else statements. For this, we can use elif as a replacement for else if; it changes nothing substantive but makes your code a lot easier to read and debug. In our example we’d write

if k = 1 then
   suffix := "st";
elif k = 2 then
   suffix := "nd";
elif k = 3 then
   suffix := "rd";
else
   suffix := "th";
end:

Suggested challenges:

  • Incorporate this block of code with the previous examples. Use %s in place of %a in printf when referencing the suffix variable to avoid getting unwanted quotes.
  • The above block of code is not accurate for all values of k, e.g. k=21. Try to fix it so that works for all possible integer values of k. The simplest version of the code will use  mod as well as the or and and boolean operators (see ?and for details).

The `if` function

Sometimes, you might want to write down a conditional statement without writing multiple lines of code, for example in the body of seq. For this you can use the `if` function, which always takes exactly three arguments,

> `if`(«test», «Tvalue», «Fvalue»);

It checks the value of the test and returns the Tvalue if the test is true, and the Fvalue if the test is false. For example, we can define the absolute value function by

> absValue := x -> `if`(x>0, x, -x);

(As we mentioned earlier, this function is already pre-defined in Maple as abs.)

Postscript (not the .ps file format)

In our examples, we checked for “approximate convergence” of the power series by comparing it to the exact value of exp(0.6) or sin(0.6). A computer which is trying to compute sin(0.6) essentially computes the value using power series. But obviously, if it’s trying to compute exp(0.6), it won’t have access to that value already. This is one reason for wanting to prove rigorous convergence bounds that give an unconditional guarantee on the error term.

These are course notes for the University of Waterloo's course Math 600: Mathematical Software.
© 2012—. Written and developed by David Pritchard and Stephen Tosh. Contact (goes to the CEMC)