looping

advertisement
Lecture 5: Repetition
While not learned
The Need for Repetition
• At the compile time, we cannot generally know how long the
program is going to run
• If the program reads its input from an arbitrary data file, we
can't know beforehand how long this file will be
• Some interactive game between human and machine can
take arbitrarily many moves, and the human might want to
play the game arbitrarily many times
• Language must have some mechanism to keep doing
something over and over, as many times as it's needed
• Recursion works in principle, but has its downsides
• Explicit looping often simpler and superior
• Three types of loop structures, all equally powerful in
principle, in practice handy in different situations
While-Loop
• Of the three different loop structures offered in C, whileloops are conceptually the simplest
• Resembles if-statement, but does not have an else
• Conceptually, keep executing the loop body again and
again while the condition is true
• Loop terminates when the loop condition becomes false
• Note that the truth of the loop condition is not checked
constantly between every statement in the loop body, but
only at the beginning of the loop, and then again between
the repetitions of the loop body
• Condition may be temporarily false during the loop, but
become true again before it is checked
Countdown With While
/* Outputs a countdown from n to 0. */
int countdown_with_while(int n) {
int c = n; /* Good style to use a separate loop counter */
while(c >= 0) {
printf("%d... ", c);
c = c - 1;
}
printf("Liftoff!\n");
}
Behaviour of While-Loops
• A while-loop will execute its body zero times, if the condition
is false to begin with (for example, call the countdown
function with a negative n)
• Normally, a while-loop will execute its body some finite
number of times and then terminate
• It is possible to have an infinite loop that never terminates
(for example, forget to write in the statement to decrement c
in the countdown function)
• Some sort of infinite loops are necessary for programs that
are supposed to run forever (until they are explicitly
terminated by user), but in this course, infinite loops are
logic errors
• Of course, the loop itself may be infinite while(1), but you
jump out of it, if some condition is true
Do-While Loop
• The second type of loop offered in C is the do-while
• Clearly the least commonly used of the three: one famous
study of real world programs revealed that out of all loops in
them, only 1% are do-while
• Do-while behaves exactly the same way as the while-loop,
but is guaranteed to execute its body at least once before
it starts looking at the loop condition
• The possibility of executing the loop body zero times does
not exist, even if the condition is initially false
• do-while is most useful in situations where testing the
condition simply does not make any sense until the loop
body has been executed once
Asking User for Constrained Input
/* Keep asking the user for a number until he types in a number
that is between min and max, inclusive. */
int ask_for_number(int min, int max) {
int num;
do {
printf("Enter integer between %d and %d: ", min, max);
scanf("%d", &num);
} while(num < min || num > max);
return num;
}
A Sentinel-Controlled Loop
int sum_of_inputs(int min, int max) {
int sum = 0, curr;
do {
curr = ask_for_number(min, max);
sum += curr;
} while(curr != 0); /* Stop when input is 0 */
return sum;
}
Rationale for For-Loops
• The third type of loop structure in C is suitable for the
common task of iterating through the values of a range of
integers, one value at the time
• For example, go through the numbers from 1 to 100
• The for-loop header defines this range, and the loop body
contains the statements you execute for each value
• To define an integer range, you need to define three things:
where it begins (1), where it ends (100), and the step size
between the consecutive values of the range (1)
• It is not a coincidence that the header of a for-loop consists
of three parts, for the three things that define a range
• We can still use while like in countdown, but using a for-loop
makes it clear that we are going through a range
Defining a Range With For-Loop
• Syntactically, a for-loop is of the form for(I; C; U) { B }
• The initialization I defines where the range starts, and is an
assignment to the loop counter variable that keeps track of
which value we are currently at
• The condition C defines where the range ends, but does so
in a negated way, be defining where the range still continues
• As long as the condition is true, we are still inside the range
and the loop executes its body B one more time
• The update U is an assignment to the loop counter that
calculates the next value in the range
• For example: for(c = 1; c <= 100; c++)
Some Example Ranges
•
•
•
•
•
•
•
•
•
Even numbers from -100 to +100:
for(c = - 100; c <= 100; c += 2) { ... }
All numbers from start to end:
for(c = start; c <= end; c++) { ... }
Powers of two from 1 to 65536 ( = 216):
for(c = 1; c <= 65536; c = c * 2) { ... }
Powers of three up to 3n:
for(c = 0, p = 1; c <= n; c++, p = p * 3) { ... }
For convenience, the last example uses two separate loop
variables, separated by the comma operator that can be
used to combine two expressions into one, used where the
language syntax requires one expression
Example: Countdown With For
/* Outputs a countdown from n to 0. Note that using a for loop,
the loop body does not need to talk about the logic of iterating
through the values, but can just contain the statements that you
want to execute for each value. */
void countdown_with_for(int n) {
int c; /* Still have to declare the loop counter variable */
for(c = n; c >= 0; c--) {
printf("%d... ", c);
}
printf("Liftoff!\n");
}
Example: Output a List of Numbers
/* Output the numbers from start to end separated by commas,
taking care that you don't write a trailing comma after the last
number in the series. */
void output_numbers(int start, int end) {
int c;
for(c = start; c <= end; c++) {
printf("%d", c);
if(c < end) { printf(", "); }
}
}
Example: Rolling Multiple Dice
/* Roll the dice given number of times and return total. */
int roll_dice(int n_dice, int sides) {
int total = 0;
int roll;
for(roll = 1; roll <= n_dice; roll++) {
total += 1 + rand() % sides;
}
return total;
}
Example: Harmonic Sum
/* H_n = 1 + 1/2 + 1/3 + ... + 1/n */
double harmonic(int n) {
double result = 0.0;
int i;
for(i = 1; i <= n; i++) {
result += 1.0 / i;
}
return result;
}
Example: Primality Testing
int is_prime(int n) {
int c;
if(n < 2) { return 0; }
if(n == 2) { return 1; }
if(n % 2 == 0) { return 0; }
/* Iterate through odd numbers from 3 to sqrt(n) */
for(c = 3; c * c <= n; c += 2) {
/* For each such number, check if it divides n. */
if(n % c == 0) { return 0; }
}
return 1; /* Only after we checked all divisors... */
}
Example: Goldbach Conjecture
/* Check if the positive even number n > 4 can be expressed as
a sum of two prime numbers. */
int verify_goldbach(int n) {
int a;
for(a = 3; a <= n / 2; a += 2) {
if(is_prime(a) && is_prime(n - a)) { return 1; }
}
/* This should not happen, assuming valid n. */
return 0;
}
Example: Fibonacci Numbers
/* 1, 1, 2, 3, 5, 8, 13, 21, 34, ... */
int fib(int n) {
int a = 1, b = 1, i, c;
for(i = 2; i <= n; i++) {
c = a + b; /* Next number in series */
a = b;
/* Shift the current two numbers */
b = c;
}
return b;
}
Example: Debt Calculator
int debt(double debt, double interest, double payment) {
int years = 0;
double old_debt;
while(debt > 0) {
old_debt = debt;
debt += debt * interest / 100.0 - payment;
if(old_debt <= debt) { return -1; }
years++;
}
return years;
}
Example: Binary Power
double power(double base, int exp) {
double result = 1.0;
if(exp < 0) { return 1.0 / power(base, -exp); }
while(exp > 0) {
if(exp % 2 == 1) { result = result * base; }
base = base * base;
exp = exp / 2;
}
return result;
}
Example: Solving a Root by Halving
/* Assume F is a continuous function of doubles defined as a
macro, and F(a) and F(b) initially have different signs. */
double solve(double a, double b) {
double mid, fmid;
while(b - a > 0.000001) { /* desired precision */
mid = (a + b) / 2; fmid = F(mid);
if(fmid > 0 && F(a) > 0) { a = mid; }
else if(fmid < 0 && F(a) < 0) { a = mid; }
else b = mid;
}
return (a + b) / 2;
}
Example: Hailstone Numbers
int count_hailstone(int n) {
int steps = 0;
while(n > 1) {
steps++;
if(n % 2 == 0) { n = n / 2; }
else { n = 3 * n + 1; }
}
return steps;
}
/* The famous open Collatz conjecture says that for any starting
value n, this process will reach 1 in finite time. */
Nested Loops
• Since the body of any looping statement can be any series
of statements, these statements can be conditions and even
other inner loops nested inside the outer loop
• It might be first difficult to intuit the behaviour of nested
loops, but this becomes easier when you just think of how
an ordinary clock works
• To count through one day, the hour counter goes through the
numbers from 0 to 23
• For each hour, the minute counter goes through the
numbers from 0 to 59, starting again the next hour
• For each minute, the second counter goes through the
numbers from 0 to 59, starting again the next minute
• Implemented next as three nested loops
Counting Time With Nested Loops
void count_time_through_day() {
int hour, minute, second;
for(hour = 0; hour < 24; hour++) {
for(minute = 0; minute < 60; minute++) {
for(second = 0; second < 60; second++) {
/* Whatever you do with the current second... */
}
}
}
}
As Long As The Total Is Correct...
• To count to five, we humans count "one, two, three, four,
five", with or without using our fingers to keep track
• If something is supposed to happen a total of five times, but
the actual loop counter values don't matter, we could just as
well count "ten, eleven, twelve, thirteen, fourteen" and it
would be just as correct
• In computing, it is often convenient to start counting from
zero, instead of one
• The canonical loop to do something exactly n times goes
for(c = 0; c < n; c++) { ... }
Off By One
• Writing loop logic, off by one is a common type of logic
error in which the loop runs one round too many or too few
• Do not try to fix this by mechanistically converting the
terminating condition from < to <=, or vice versa, but reason
for what values the loop is supposed to run
• Common subtype is fencepost error: if there is a fencepost
every 10 meters, and the fence is 100 meters long, how
many fenceposts are there?
• How many integers are there from +10 to +20?
• How about from -10 to +10?
• Fencepost errors can be avoided by using zero-based
indexing, and expressing ranges with an exclusive end
Example: Output A Rectangle
/* Output a filled rectangle of given character using nested
loops, outer loop counting the rows, and for each row, the inner
loop counting the columns. */
void output_rectangle(int rows, int cols, char ch) {
int r, c;
for(r = 0; r < rows; r++) {
for(c = 0; c < cols; c++) {
printf("%c", ch);
}
printf("\n"); /* Newline after each row */
}
}
Example: Output a Deck of Cards
void output_deck() {
int suit, rank;
for(suit = 0; suit < 4; suit++) {
for(rank = 1; rank <= 13; rank++) {
switch(rank) {
case 11: printf("jack of "); break;
case 12: printf("queen of "); break;
case 13: printf("king of "); break;
case 1: printf("ace of "); break;
default: printf("%d of ", rank); break;
}
switch(suit) {
case 0: printf("clubs\n"); break;
case 1: printf("diamonds\n"); break;
case 2: printf("hearts\n"); break;
case 3: printf("spades\n"); break;
}
}
}
}
Example: Output Prime Factors
void output_prime_factors(int n) {
int c = 2; /* First potential factor */
while(n >= c) {
if(n % c == 0) {
printf("%d ", c); /* Eliminate one factor */
n = n / c;
}
else { /* Move on to try the next potential divisor */
c += (c == 2)? 1: 2;
}
}
printf("\n");
}
Functions Simulating Nested Loops
void output_one_row(int cols, char ch) {
int c;
for(c = 0; c < cols; c++) {
printf("%c", ch);
}
printf("\n");
}
void output_rectangle(int rows, int cols, char ch) {
int r;
for(r = 0; r < rows; r++) {
output_one_row(cols, ch);
}
}
Example: Radix Conversions
/* First, a utility function to return the digit character for the
given digit. Assumes that digit is at most 36. */
char get_digit(int d) {
if(d < 10) { return '0' + d; }
else { return 'A' + (d - 10); }
}
/* The character '0' is not the same as zero. Since characters
are really integers, it is okay to perform integer arithmetic on
them. The function assumes that characters '0', ..., '9' are
consecutive in the encoding table, as are also 'A', ..., 'Z'.*/
Radix Conversion Using While
void output_int(int n, int base) {
int p = 1;
while(p * base <= n) { p = p * base; }
/* p is now the highest power of base that is <= n. */
while(p > 0) { /* Descend through the powers of base
printf("%c", get_digit(n / p) );
n = n % p;
p = p / base;
}
}
Radix Conversion With Recursion
/* In this particular problem, recursion is so much simpler that I
just have to show it. The loop was complicated because we
have to output digits starting with the most significant one,
which is harder to find than the least significant digit. With
recursion, this order doesn't matter. */
void output_int(int n, int base) {
/* Output the digits before the last one. */
if(n >= base) { output_int(n / base); }
/* Output the last digit. */
printf("%c", get_digit(n % base));
}
Terminating Loops Prematurely
• Inside a loop, a break statement causes the execution to
immediately jump out of that loop, and move to the
statement following the loop
• If used inside nested loops, break jumps out of the
innermost loop that contains this statement
• If you want to jump out of an outer loop, you need to first
give this loop a label, an identifier followed by a colon
placed before the loop, and use this label in the break
statement to specify which loop you want to terminate
• In principle, any loop that uses break can be rewritten as an
equivalent loop that does not use that statement
• Jumping around tends to bring back the bad old days of
unstructured spaghetti code and makes the program logic
harder to follow
Continue
• continue is a statement similar to break, but instead of
jumping out of the loop altogether, it jumps to the next round
of the loop, skipping the rest of the body
• Somewhat confusingly named, since this statement would
have been better titled maybe skip or next
• Most commonly used to skip some of the values inside the
range for which the loop doesn't need to do anything
• Just like with break, you can use a label to determine which
loop you want to move to the next round
• In modern C, did you know you can put URL's in code?
• http://www.ryerson.ca
Files With Unknown Amount of Data
• When reading data from a file so that the file doesn't in its
beginning tell us how many items of data it contains, we
need a way to tell that we have reached the end of file
• Function feof(f) tells you this for the file f
• Use something like while(!feof(f)) { ... }
• Alternatively, can use the fact that fscanf, in addition to
assigning the values, returns the constant EOF if you have
already reached the end of file
• Use something like while(fscanf(...) != EOF) { ... }
• Better when there is extra whitespace at end of file
• Also works with scanf just as well as with fscanf
Example: Variance from File
double variance(FILE* f) {
double sum = 0, sum_sq = 0, x, avg_sq, avg;
int total = 0;
while(fscanf(f, "%f", &x) != EOF) {
sum += x; sum_sq += x * x;
total++;
}
avg = sum / total;
avg_sq = sum_sq / total;
return avg_sq - avg * avg;
}
/* It is the caller's responsibility to open and close the file. */
Raw Text, The Universal Data Format
• Storing numbers as text takes more space than storing them
as bytes, since you effectively use only ten of the 256
possible values of each byte (plus whatever whitespace,
comma or other separator characters you use)
• Application-specific binary data formats can take a lot less
space compared to human-readable text
• However, text can be easily edited and transformed, and it
does not impose hard constraints on the maximum values of
each data field when stored in a fixed space
• Prefer text files over custom binary formats, unless there
really is a large amount of data (sound, pictures, video)
• Can always compress and decompress text anyway with
standard compression algorithms
"When I am working on a problem, I never think about beauty. I
think only of how to solve the problem. But when I have
finished, if the solution is not beautiful, I know it is wrong."
Buckminster Fuller
"John von Neumann draws attention to what seemed to him a
contrast. He remarked that for simple mechanisms, it is often
easier to describe how they work than what they do, while for
more complicated mechanisms, it is usually the other way
around."
Edsger W. Dijkstra
Download