10B. Planning Loops


Computing integer powers

Let's look at the problem of computing xn, where x and n are integers and n > 0. For example, 24 = 2×2×2×2 = 16.

Since xn = x×x×…×x, repetition seems to be called for.

It is not a good idea to proceed directly to writing C++ code. Instead, we will take an approach called pre-simulation, where we begin by performing a hand simulation of an algorithm that we have in mind.


Planning based on pre-simulation

Think about how you would solve the problem by hand

How would you compute 24 by hand? An obvious approach is to start at 2 and successively multiply by 2 until you have 24. The intermediate results are 2, 4, 8 and 16.

Now solve one or two examples by hand, in some detail

Computing 34 seems like a reasonable example. But now it is important to begin tying down variables. Start with 3, and successively multiply by 3 a total of 3 times. If the power of 3 is stored in variable p, then p changes as follows.

     p
     3
     9
    27
    81


Decide on control variables

It is clear that you need variable p. But that cannot be enough. How do you know when you have the result? When doing the computation by hand, you are keeping a count in your head. That needs to be brought out into the open. Another variable, k, is called for; it tells the power of 3 stored in variable p.

     p    k    x     n
     3    1    3     4    (p = x1)
     9    2               (p = x2)
    27    3               (p = x3)
    81    4               (p = x4)

Write the pieces of the loop based on the pre-simulation

Examining the pre-simulation, initialization of p and k should clearly be
  p = x;
  k = 1;
The following statements serve to update p and k.
  p = p * x;
  k = k + 1;

Decide when to end the loop

How can we recognize the last line of the pre-simulation, where p is the answer? What makes it different from the other lines? Clearly, k = n. The loop should keep going as long as kn.

Put the pieces together

Here is a definition of function power(x, n) based on that plan.
  // power(x,n) returns x to the n-th power.
  //
  // Requirement: n > 0.

  int power(const int x, const int n)
  {
    int k = 1;
    int p = x;
    while(k != n)
    {
      p = p * x;
      k = k + 1;
    }
    return p;
  }

Check your work with a hand simulation of what you wrote.

The loop that you wrote should give exactly the same hand simulation as the pre-simulation. Here is the entire simulation for x = 3 and n = 4.
     p    k    n
     3    1    4
     9    2
    27    3
    81    4


Another example: greatest common divisor

Suppose that x and y are two nonnegative integers, not both 0. The greatest common divisor gcd(x, y) of x and y is the largest integer that is a divisor (or factor) of x and y. For example, gcd(15, 40) = 5.

Long ago, Euclid determined the following facts, where x mod y is the remainder when you divide x by y. (It is written x % y in C++).

gcd(x, 0) = x  (x ≠ 0)
gcd(x, y) = gcd(y, x mod y)  (y ≠ 0)

Let's plan and write function gcd(x, y).

Try an example by hand

You can compute gcd(15, 40) by hand using Euclid's equations.

gcd(15, 40) = gcd(40, 15)  since 15 mod 40 = 15
  = gcd(15, 10)  since 40 mod 15 = 10
  = gcd(10, 5)  since 15 mod 10 = 5
  = gcd(5, 0)  since 10 mod 5 = 0
  = 5  by the first equation above

Decide on control variables and do a pre-simulation

At each step, there are two numbers, the two parameters of gcd in Euclid's equations. Let's call those two numbers m and n.

A pre-simulation of gcd(15, 40), where x = 15 and y = 40, looks similar to the idea above, but shows more detail.

      m     n     x     y
     15    40    15    40
     40    15
     15    10
     10     5
      5     0

Write the pieces of a loop based on the pre-simulation

The initialization should be clear.
  m = x;
  n = y;
How should m and n be updated? Notice that it will not work to say
  m = y;
  n = x;
That correctly produces the second line for the special case where x = 15 and y = 40. But it does not work to produce subsequent lines, and does not even produce the second line correctly when x = 40 and y = 15.

We need code that updates m and n correctly regardless of their values (as long as n > 0). But, as long as we are careful to think in general terms, looking at the first two lines should lead us to reasonable update code. Let's try the following, based on Euclid's equations.

  m = n;
  n = m % n;
The first two lines of the pre-simulation above are as follows.
      m     n     x     y
     15    40    15    40
     40    15
Now let's do a careful hand simulation the proposed update code to see whether it produces the next line of the simulation correctly. The starting point is the first line.
      m     n
     15    40
The first statement m = n; of the proposed update code,
  m = n;
  n = m % n;
stores the value of n into m, replacing the former value of m.
      m     n
     40    40
The second statement stores 40 % 40, which is 0, into n.
      m     n
     40     0
That does not match the desired pre-simulation, so we need to change the update code.

Would it work to write the statements in the opposite order?

  n = m % n;
  m = n;
That still does not work. Let's write m0 and n0 for the values of m and n in the first line, and write m1 and n1 for the values of m and n in the second line of the pre-simulation. Then
  n1 = m0 % n0;
  m1 = n0;
Now the issue is clear. The updated value m1 of m depends on the old value n0 and the updated value n1 of n depends one the old value m0. One way to fix the update code is to remember the old values of m and n before changing m and n.
  int oldm = m;
  int oldn = n;
  m = oldn;
  n = oldm % oldn;
That works. (Do a hand simulation of it to show that it correctly updates m and n.)


Decide when to end the loop

The loop should continue as long as n > 0. Euclid tells us that gcd(m, 0) = m, so when n = 0, the loop should stop; the result is m.

Put the pieces together

  int gcd(const int x, const int y)
  {
    int m = x;
    int n = y;
    while(n != 0)
    {
      int oldm = m;
      int oldn = n;
      m = oldn;
      n = oldm % oldn;
    }
    return m;
  }


Exercises

  1. Suppose that n is a positive integer. A proper divisior of n is an integer k where 0 < k < n and k is a divisor (or factor) of n. For example, 2 is a proper divisor of 6. Notice that, if 0 < k < n, you can tell whether k is a proper divisor of n by testing whether n%k == 0.

    Say that n is perfect if n is equal to the sum of all of its proper divisors. For example, 6 is perfect because the proper divisors of 6 are 1, 2 and 3 and 1 + 2 + 3 = 6.

    Write a definition of function isPerfect(n), which returns true if n is perfect. Plan the loop before starting to code.

    Answer