Add 1 to s |
Add 2 to s |
Add 3 to s |
... |
Add n to s |
There is a problem with this. A while loop will do one thing over and over. But the lines that you have so far keep changing; no two of them are exactly the same. There first says to add 1 to s, the second says to add 2 to s, etc. You need a variable to hold the value to be added. Let's call that variable k. Set k = 1 initially. Then the actions to be performed are
Add k to s |
Add 1 to k |
Add k to s |
Add 1 to k |
Add k to s |
Add 1 to k |
... |
Add k to s (where k is n at this line) |
Add 1 to k |
Now you see exactly the same thing over and over. Just repeat
Add k to s |
Add 1 to k |
int k,s; s = 0; k = 1; while(k <= n) { s = s + k; k = k + 1; }
To see what is going on, make a table showing two variables. One variable holds the partial sum, and the other tells how much of the sum you have.
k | s | |
0 | 0 | |
1 | 1 (= 0 + 1) | |
2 | 3 (= 0 + 1 + 2) | |
3 | 6 (= 0 + 1 + 2 + 3) | |
4 | 10 (= 0 + 1 + 2 + 3 + 4) | |
... | ||
n | 0 + 1 + 2 + 3 + ... + n |
Now you are ready to think about writing the loop. Clearly, you need two variables, k and s. Each should have initial value 0 (given by the first line of the table). The loop should stop when k == n, so it should only keep going as long as k != n.
What about updating the values of k and n, going from one line to the next? To get from
k | s | |
2 | 3 (= 0 + 1 + 2) |
k | s | |
3 | 6 (= 0 + 1 + 2 + 3) |
int k,s; k = 0; s = 0; while(k != n) { k = k + 1; s = s + k; }
s = 1 + 2 + ... + kFor example, when s = 3 and k = 2, the assertion is true. When s = 10 and k = 4 the assertion is true. No matter what line of the table you choose, the assertion is true. Since this assertion is true at every line, it is invariantly true. It is therefore called a loop invariant.
A loop invariant is a kind of assertion. We will use a special, technical definition of the word assertion. An assertion is a statement that is true or false, and is based solely on the current values of variables. Think of taking a snapshot of the variables at a certain point in the program. An assertion can only talk about what is seen in the snapshot. It cannot talk about what happened before or after the snapshot. For example, if i and j are variables, then
i > jis an assertion, since whether it is true or false depends only on the current values of i and j. On the other hand,
1 has been added to iis not an assertion, since this statement is trying to relate the current value of i to a former value of i.
When you try to write a loop invariant, make sure that your loop invariant is an assertion.
In general, you can set s = 1, and then multiply s by x a total of n times. To do this, you need a counter (k) telling how many times you have multiplied x into s. The function definition looks like this.
double power(double x, int n) { double s; int k; s = 1.0; k = 1; while (k <= n) { s = x * s; k++; } return s; }
The action-oriented design is simple if you see it, but it offers little help if you don't see it. Data-oriented design offers a little more help.
To compute s = xn, you compute higher and higher powers of x. You will want a value k telling us what power you have so far. The loop invariant is
s = xkLet's write a table showing the values that k and s will take on as the loop progresses. You need an example, so choose x = 2.0 and n = 5. Notice that, for each line of the table, the loop invariant is true. (In fact, the loop invariant tells you what to write in the table.)
k s 0 1.0 1 2.0 2 4.0 3 8.0 4 16.0 5 32.0Check that s = xk at each line. Now you need to write the loop. Clearly, k should initially be 0 and s should initially be 1. (Read this from the first line of the table.)
The last line is special because k == n. So only keep looping as long as k != n. Also notice that, in the last line, the desired value is in variable s.
Now think about how to get from one line to the next. Evidently, you add 1 to k and multiply s by x. The function definition goes as follows.
double power(double x, int n) { double s; int k; s = 1.0; while (k != n) { s = x * s; k = k + 1; } return s; } }Notice how the data-oriented design leads you to a correct solution. Once you have chosen the invariant, everything else falls out quite easily. With this approach, you often get a loop that works the first time.
In some situations, action-oriented loop design is simple and natural. If you see how to solve a problem using action-oriented design, use that. In more complex situations, you can find it difficult to think of the actions to perform. In those cases, switch to data-oriented loop design. It will often lead you in the right direction.
Experience by professional programmers has shown that data-oriented loop design combined with loop invariants is more reliable, and tends to lead to correct programs more often than action-oriented design.