The Department of Computer Science & Engineering |
STUART C. SHAPIRO: CSE
305
|
According to a theorem proved by Corrado Böhm and Giuseppe Jacopini (CACM, 1966), any procedure can be written with the following control structures:
break
(or
exit
) statement is added, code repetition is not needed.
Note, that in the Preliminaries notes, I said that among the defining criteria of being a programming language was the facilities for sequence, selection, and loop.
A standard part of any imperative program is a sequence of statements, to be executed in sequential order.
Fortran indicates statement sequence by line sequence, but also allows a sequence of statements on one line separated by semi-colons (;).
The semi-colon (;) started out as a statement separator, but it turned out to be cleaner syntax to make it a statement terminator, as it is in most current languages.
A compound statement is a sequence of statements treated
syntactically as one statement. The statements in a compound
statement are usually surrounded by braces ({ ... }
), but
some languages use begin ... end
or other bracketing
keywords.
A block is a compound statement in which variables may be declared with scope limited to that compound statement.
Prolog, a logical (relational) programming languages also uses sequence as a basic control structure.
Functional programming languages get sequential execution from the
nesting of function calls.
Let's compare sequential execution in:
The Ruby program:
def quadRoots(a,b,c) # Returns an array of the two roots of the quadradic equation, ax^2 + bx + c = 0 discrim = Math.sqrt(b*b - 4*a*c) return (-b + discrim)/(2*a), (-b - discrim)/(2*a) end a,b,c = 2,7,5 firstroot, secondroot = quadRoots(a,b,c) # Should be [-1.0, -2.5] puts "The roots of #{a}x^2 + #{b}x + #{c} = 0 are #{firstroot} and #{secondroot}" ---------------------------------------------------------------------------- <timberlake:Test:1:53> ruby quadRoots.rb The roots of 2x^2 + 7x + 5 = 0 are -1.0 and -2.5
The Prolog program:
quadRoots(A,B,C,[Firstroot,Secondroot]) :- Discrim is sqrt(B*B - 4*A*C), Firstroot is (-B + Discrim)/(2*A), Secondroot is (-B - Discrim)/(2*A). :- [A,B,C] = [2,7,5], quadRoots(A,B,C,[P,N]), format("The roots of ~dx^2 + ~dx + ~d = 0 are ~g and ~g~n", [A,B,C,P,N]), halt. ---------------------------------------------------------------------------- <timberlake:Test:1:57> prolog -l quadRoots.pro % compiling /projects/shapiro/CSE305/Test/quadRoots.pro... The roots of 2x^2 + 7x + 5 = 0 are -1.0 and -2.5 % compiled /projects/shapiro/CSE305/Test/quadRoots.pro in module user, 10 msec 2016 bytes
The Erlang program:
-module(quadRoots). -export([main/0,quadRoots/3,printQuadRoots/3]). discrim(A,B,C) -> math:sqrt(B*B - 4*A*C). twoRoots(A,B,Discrim) -> {(-B + Discrim)/(2*A), (-B - Discrim)/(2*A)}. quadRoots(A,B,C) -> % Returns a tuple of the two roots of the quadradic equation, ax^2 + bx + c = 0 twoRoots(A,B,discrim(A,B,C)). printRoots(A,B,C,{Firstroot, Secondroot}) -> io:format("The roots of ~wx^2 + ~wx + ~w = 0 are ~w and ~w~n", [A,B,C,Firstroot,Secondroot]). printQuadRoots(A,B,C) -> printRoots(A,B,C,quadRoots(A,B,C)). main() -> printQuadRoots(2,7,5), halt(). ---------------------------------------------------------------------------- <timberlake:Test:1:54> erl -compile quadRoots.erl <timberlake:Test:1:55> erl -noshell -s quadRoots main The roots of 2x^2 + 7x + 5 = 0 are -1.0 and -2.5
goto
<label>
, where <label>
is a symbolic or
numeric label of some statement. (We will assume symbolic labels.)
Generally, a statement is labeled using the syntax <label>:
<statement>
.
Some languages allow a goto target that is the result of a run-time computation.
There are two major objections to the use of goto:
andx = 3.0; y = z / x;
The first obviously does not involve a divide by zero, but consider what you have to do to check that the second does not.x = 3.0; here: y = z / x;
The use of the goto was challenged by Edsger Dijkstra in his famous letter to the editor, "Goto Statement Considered Harmful" (CACM 1968). This launched the structured programming movement. Since the Böhm/Jacopini theorem proved that the goto is not needed, several subsequent languages downplayed, or even eliminated the goto statement. Java, Python, and Ruby, for example, do not have it.
exit
statement is an executable statement whose
effect is to immediately continue execution at the statement
immediately following the lexically innermost containing control
structure. What control structures may be exited from is
language-dependent.
The exit statement can eliminate the need for repeated code when using only the sequence, selection, and loop control structures. For example, consider the following HOSL program for reading and processing some input file:
Using aloop: input := read(file); if (input = eof) goto out; process(input); goto loop; out: ...
while
, this may be written as:
Note the repetition ofinput := read(file); while (input != eof) { process(input); input := read(file); } ...
input := read(file);
. However,
using exit, this may be rewritten as:
while (true) {
input := read(file);
if (input == eof) exit;
process(input);
}
...
Of course, if we can combine reading and testing, this use of exit
can be avoided:
while ((input := read(file)) != eof) {
process(input);
}
or in C,
while (scanf(format, input) != EOF) {
process(input);
}
Nevertheless, sometimes exit
is the only way to avoid
repeated code.
A single-level exit
always exits from the lexically
innermost control structure. A multi-level exit
may exit
from a more distant lexically containing control structure. In some
languages, the multi-level exit
takes a numerical
parameter, i, and exits from the ith
containing structure. In others, the multi-level exit
takes a symbolic parameter, label, and exits from the control
structure labeled label. This use of a label differs from, and
is not as dangerous as the statement label used as the target of a
goto. Multi-level exit
statements can be used to avoid
code repetition that is needed if only the single-level
exit
is available.
In C-based languages, break
is used as the exit
statement. In C, C++, Python, and Ruby break
is a single-level exit.
In Java it is a multi-level exit, taking an optional symbolic label.
Perl uses last
as a multi-level exit with an optional
symbolic label.
In Common Lisp (return-from <label>
[<expression>])
is a multi-level exit. (return
[<expression>])
is equivalent to (return-from nil
[<expression>])
, and serves as a single-level exit.
- Selection
-
- The if Statements
-
- Single-Branch Selection
- The single-branch selection statement is usually of the form
if (<expression>) <statement>
, where
<statement>
could be a simple statement, a compound
statement, or a block. If the <expression>
evaluates
to a true value the <statement>
is executed.
Otherwise it isn't. In either case, the next statement done is the
statement following the if
statement.
Perl and Ruby have both an if (<expression>)
<compound-statement>
and an unless (<expression>)
<compound-statement>
. The latter executes the
<compound-statement>
if and only if the
<expression>
is false. A
<compound-statement>
, complete with its opening and
closing bracket indicators, is required even if there is only one
statement in it.
Common Lisp's two single-branch selection forms are (when
<expression> <form>+)
and (unless <expression>
<form>+)
.
The following Ruby program shows several ways to express the
single-branch selection statement:
- in one line using
then
and end
;
- with a newline instead of
then
, an indented
body, and end
;
- the "modifier" form in which
if
and the
condition follows the modified statement.
Also note the use of the splat in the actual argument.
def sort3(x,y,z)
# Returns the array [x,y,z] sorted, smallest first.
if x>y then x,y = y,x end
if y>z
y,z = z,y
x,y = y,x if x>y
end
return x,y,z
end
a = [4,3,2]
puts "The result of sorting #{a} is #{sort3(*a)}."
a = [4,2,3]
puts "The result of sorting #{a} is #{sort3(*a)}."
a = [3,4,2]
puts "The result of sorting #{a} is #{sort3(*a)}."
a = [3,2,4]
puts "The result of sorting #{a} is #{sort3(*a)}."
--------------------------------------------------
<timberlake:Test:1:70> ruby sort3.rb
The result of sorting [4, 3, 2] is [2, 3, 4].
The result of sorting [4, 2, 3] is [2, 3, 4].
The result of sorting [3, 4, 2] is [2, 3, 4].
The result of sorting [3, 2, 4] is [2, 3, 4].
In Prolog, since every term either succeeds or fails, every clause is
a single-branch selection:
ltCheck(X,Y) :- X=<Y, format("~d and ~d are in order.", [X,Y]).
---------------------------------------------------------------
<timberlake:Test:1:76> prolog
SICStus 4.0.5 (x86_64-linux-glibc2.3): Thu Feb 12 09:48:30 CET 2009
Licensed to SP4cse.buffalo.edu
| ?- ['ltCheck.pro'].
% compiling /projects/shapiro/CSE305/Test/ltCheck.pro...
% compiled /projects/shapiro/CSE305/Test/ltCheck.pro in module user, 0 msec 720 bytes
yes
| ?- ltCheck(3,5).
3 and 5 are in order.
yes
| ?- ltCheck(5,3).
no
| ?- ltCheck(4,4).
4 and 4 are in order.
yes
- Double-Branch Selection
- The double-branch selection statement is usually of the form
if (<expression>) <then-statement> else
<else-statement>
and is often referred to as the
if-then-else statement. If the <expression>
evaluates
to a true value, <then-statement>
is executed.
Otherwise <else-statement>
is. In either case, only
one of the two statements is executed, and execution continues with
the statement following the if satement. This is the selection
referred to in the Böhm/Jacopini theorem. Of course the then
statement could always be empty, reducing to a single-branch selection
statement.
The if-then-else statement gave rise to a famous case of syntactic
ambiguity. When is the else statement executed in a case like
if (test1)
if (test2)
statement1
else
statement2
The two possibilities are: 1) if test1 is true and test2 is false; 2) if
test1 is false. In current languges, this is generally solved by a
rule that matches the else with the nearest unmatched if. Thus, in
the example above, statement2 would be done in the case that test1 is
true and test2 is false. If the other case were wanted, brackets or a
special keyword need to be used. In Java, brackets would be used to
turn the inner if statement into a one-statement compound statement:
if (test1) {
if (test2)
statement1
}
else
statement2
Other languages end every if with a keyword such as end
if
, making the two cases
if (test1)
if (test2)
statement1
else
statement2
end if
end if
and
if (test1)
if (test2)
statement1
end if
else
statement2
end if
Common Lisp's double-branch selection expression is
(if test
then-expression
else-expression)
- Multi-Branch Selection
- The multi-branch selection that is the most straightforward
extension of the double-branch selection chooses one of multiple
statements to execute based on the first of multiple tests to evaluate
to true. In the C-based languages, it can be expressed as a nested set
of (right-associative) if-then-elses:
if (test1) statement1
else if (test2) statement2
else if (test3) statement3
...
else if (testn) statementn
else default-statement
Notice that if we used indentation and brackets to indicate statement
nesting, this would be
if (test1) {statement1}
else {if (test2) {statement2}
else {if (test3) {statement3}
...
else {if (testn) {statementn}
else {default-statement}}...}}
To flatten this, even syntactically, some languages combine the
else
with the subsequent if
giving an
elseif
(or elif
) keyword. In Python, the
above would look like
if test1:
statement1
elif test2:
statement2
elif test3:
statement3
...
elif testn:
statementn
else:
default-statement
and does not involve nested selection statements, though the tests are
still done in order.
(The C-based languages tend not to have a syntactically distinct
multi-branch if statement.)
We can express the general idea in EBNF as
<if statement> -> if <condition> then <statement>
{elseif <condition> then <statement>}
[else <statement>]
endif
Common Lisp's multiple-branch selection expression looks like this
latter version:
(cond (test1 expression11 expression12 ...)
(test2 expression21 expression22 ...)
(test3 expression31 expression32 ...)
...
(t default-expression1 default-expression2 ...))
For example, consider the Lisp function:
(defun stringCompare (str1 str2)
"Returns 'smaller, 'greater, or 'equal
depending on whether str1 is less than,greater than, or equal to str2."
(cond ((string< str1 str2) 'smaller)
((string> str1 str2) 'greater)
(t 'equal)))
-------------------------------------------------------------
<timberlake:Test:1:80> composer
International Allegro CL Enterprise Edition
...
cl-user(1): :cl stringCompare
;;; Compiling file stringCompare.cl
;;; Writing fasl file stringCompare.fasl
;;; Fasl write complete
; Fast loading /shared/projects/shapiro/CSE305/Test/stringCompare.fasl
cl-user(2): (stringCompare "aa" "aaa")
smaller
cl-user(3): (stringCompare "abc" "aba")
greater
cl-user(4): (stringCompare "abcd" "abcd")
equal
cl-user(5): :ex
; Exiting
Erlang looks very similar:
-module(stringCompare).
-export([stringCompare/2]).
stringCompare(Str1,Str2) ->
% Returns 'smaller, 'greater, or 'equal
% depending on whether str1 is less than,greater than, or equal to str2.
if
Str1<Str2 -> smaller;
Str1>Str2 -> greater;
true -> equal
end.
--------------------------------------------------------
<timberlake:Test:1:83> erl
Erlang R13B03 (erts-5.7.4) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.7.4 (abort with ^G)
1> c(stringCompare).
{ok,stringCompare}
2> stringCompare:stringCompare("aa", "aaa").
smaller
3> stringCompare:stringCompare("abc", "aba").
greater
4> stringCompare:stringCompare("abcd", "abcd").
equal
5> q().
ok
However, what is allowed as a "guard" in Erlang is limited. For
example, user-defined functions are not allowed. (See Erlang/OTP
System Documentation, section 5.7.24.)
Prolog also looks somewhat similar:
stringCompare(Str1,Str2,Result) :-
%% Returns 'smaller, 'greater, or 'equal
%% depending on whether str1 is less than,greater than, or equal to str2.
Str1 @< Str2, Result=smaller;
Str1 @> Str2, Result=greater;
Result=equal.
------------------------------------------------------
<timberlake:Test:1:84> prolog
SICStus 4.0.5 (x86_64-linux-glibc2.3): Thu Feb 12 09:48:30 CET 2009
Licensed to SP4cse.buffalo.edu
| ?- ['stringCompare.pro'].
% compiling /projects/shapiro/CSE305/Test/stringCompare.pro...
% compiled /projects/shapiro/CSE305/Test/stringCompare.pro in module user, 0 msec 720 bytes
yes
| ?- stringCompare("aa", "aaa", X).
X = smaller ?
yes
| ?- stringCompare("abc", "aba", X).
X = greater ?
yes
| ?- stringCompare("abcd", "abcd", X).
X = equal ?
yes
| ?- halt.
These are all similar in syntax to Edsger Dijkstra's guarded if:
if <test1> -> <statement1>
[] <test2> -> <statement2>
[] ...
[] <testn> -> <statementn>
fi
However, the semantics are different:
- Standard multi-branch if:
- Evaluate the tests in order and execute the statement
(evaluate the expressions) of the first test that
evaluates to true. If none are true, imperative languages
tend to do nothing, expression languages typically
evaluate to false, or give a run-time error.
- Dijkstra's guarded if
- Evaluate all the tests, and, nondeterministically, execute the
statement of one of the tests that evaluates to true. If none of the
tests evaluates to true, it is a run-time error.
Note that nondeterministic execution is not captured by the
Böhm/Jacopini control structures, but can be simulated by random
selection.
- The case Statement
- The case statement, introduced by ALGOL W (1966), is a special
purpose multiple-branch selection statement for use when all the tests
test the same expression for equality to various values.
The general form of the case statement is captured by the EBNF for
the Ruby version:
case <test expression>
{when <key expression> {, <key expression>} then <result expression>}
[else <default expression>]
end
The <test expression>
is evaluated once and compared
to each <key expression>
in order. The
<result expression>
of the first <key
expression>
that passes the comparison is evaluated, and
its value is the value of the case
expression. If
none passes the test and there is an <default
expression>
, then it is evaluated and its value is
returned as the value of the case
expression. If no
<key expression>
passes the test and there is no
else
, then the value of the case
expression is nil
.
Ruby compares the <test expression>
to each
<key expression>
using the ===
operator.
<lhs> === <rhs>
is true if
<lhs> == <rhs>
<rhs>
is in the range <lhs>
- the string
<rhs>
matches the regular expression <lhs>
<rhs>
is an instance of the class <lhs>
Here's a Ruby program to read numeric scores and convert them to
letter grades according to this course's curve. It uses a
while
loop, a single-branch if
, a
double-branch if
, and a case
expression.
def grade(x)
# Returns the letter grade corresponding to the numerical percentage x
case x
when 0..39 then :F
when 40..53 then :D
when 54..59 then :"D+"
when 60..62 then :"C-"
when 63..66 then :C
when 67..69 then :"C+"
when 70..74 then :"B-"
when 75..79 then :B
when 80..84 then :"B+"
when 85..89 then :"A-"
when 90..100 then :A
end
end
while true
puts "Enter a numeric score between 0 and 100, `done' when finished."
input = gets.chomp
exit if input == "done"
score = input.to_i
if (0..100) === score
puts "The score #{score} corresponds to a grade of #{grade(score)}."
else
puts "The number #{score} is not a valid numeric score between 0 and 100."
end
end
-------------------------------------------------------
<timberlake:Test:1:57> ruby gradeProg.rb
Enter a numeric score between 0 and 100, `done' when finished.
92
The score 92 corresponds to a grade of A.
Enter a numeric score between 0 and 100, `done' when finished.
68
The score 68 corresponds to a grade of C+.
546
The number 546 is not a valid numeric score between 0 and 100.
Enter a numeric score between 0 and 100, `done' when finished.
done
<timberlake:Test:1:58>
Common Lisp's case expression is syntactically similar:
(case keyform
(keylist1 expression11 expression12 ...)
(keylist2 expression21 expression22 ...)
(keylist3 expression31 expression32 ...)
...
(t default-expression1 default-expression2 ...))
However, the semantics is a bit different. Each keylist
must be a list of literal values (they are not evaluated). To
evaluate the case
expression, the keyform
is
evaluated. If its value is listed in one of the keylist
s
the expression
s of that keylist
are
evaluated in order, and the value of the case
expression
is the value of the last such expression
. If the value
of the keyform
is not listed in one of the
keylist
s, and the optional t
case is
present, the default-expression
s are evaluated, and the
value of the last one of those is the value of the case
expression. No key may appear in more than one keylist
.
Common Lisp also has typecase
to test the type of the
value of the keyform
.
Several languages limit the case keys to be ordinal values. That
way, the case statement may be compiled into a table of instructions
indexed by the key.
The case statement of the C-based languages is
switch (keyform) {
case key1: statement11 statement12 ...
case key2: statement21 statement22 ...
case key3: statement31 statement32 ...
...
[default: default-statement1 default-statement2 ...]
}
These keys are limited to ordinals, and there can only be one key per
case. To handle the situation of allowing the same set of statements
to be executed for several different keys, the C-based languages
specify that control flows from the last statement of the chosen case
directly through to the first statement of the next listed case. If
the programmer does not want this to happen, a break
statement must be used. For example,
switch (keyform) {
case 1:
case 3:
case 5:
case 7:
case 9: statement-odd;
break;
case 2:
case 4:
case 6:
case 8: statement-even;
break;
default: statement-too-big;
}
Often, there is only one key in each case, and the
break
can easily be forgotten, which makes the program
act not as it was intended to.
- Loop
-
- Iterative Loops
-
- Logically Controlled Loops
-
- Pretest Loops
- The pretest logically controlled loop, referred to as the while
loop, is the loop considered in the proof of the Böhm/Jacopini
theorem. The version used in the C-based languages is a typical
example:
while (test) statement
The semantics of this (in HOSL) is:
loop: if not test goto out
statement
goto loop
out: ...
Notice that test
is evaluated each time around the loop,
and that statement
might never be executed.
- Posttest Controlled Loops
- The C-based languages also have a posttest logically controlled
loop, called the do-while:
do statement while (test);
Its semantics is:
loop: statement
if test goto loop
Notice that statement
is always executed at least once,
and, again test
is evaluated each time around the loop.
A variant that several languages have is called the repeat-until
loop and looks like:
repeat statement until test;
Its semantics is:
loop: statement
if not test goto loop
The repeat-until is very similar to the do-while, but often easier to
think about because of the opposite sense of its test.
- Loop Forever
- The most flexible iterative loop is the one that loops forever,
until an exit statement is executed within its body. The Common Lisp
version is
(loop {expression})
In many languages, it may be simulated by
while (true) statement
or, in C-based languages by
for (;;;) statement
Recall the earlier example of using the exit statement to process an entire file:
while (true) {
input := read(file);
if (input == eof) exit;
process(input);
}
- Counter-Controlled Loops
- The oldest counter-controlled loop, Fortran's DO loop illustrates
all the issues:
DO label variable = initial-expression, terminal-expression [, stepsize-expression]
statements
label last-statement
next-statement
The semantics of this are [Sebesta, p. 331-332]
init-value := initial-expression
terminal-value := terminal-expression
step-value := stepsize-expression
variable := init-value
iteration-count := max(int((terminal-value - init-value + step-value)
/ step-value),
0)
loop: if iteration-count <= 0 goto out
statements
label: last-statement
variable = variable + step-value
iteration-count = iteration-count - 1
goto loop
out: next-statement
Note:
- The loop parameter expressions are evaluated only once, so changing variables
that are part of them, doesn't affect the number of times the loop is
executed.
- The number of times the loop is exected is controlled by the
iteration-count, not the value of the variable, so assigning to the
loop variable inside the loop doesn't affect the number of times the
loop is executed.
- The loop label is available to be the target of goto's inside the
loop body. The effect would be to skip the rest of the loop body
(except the last-statement---therefore the CONTINUE statement) and
continue with the next iteration.
- The scope of the loop variable is not limited to the loop body,
and it retains its last value when the loop is terminated.
The counter-controlled loop statement of the C-based languages is
different in several respects from Fortran's DO loop. Their format
is:
for (init-expr1, ..., init-exprk;
terminal-expr1, ..., terminal-exprn;
step-expr1, ..., step-exprm)
statement
The semantics is
{ init-expr1
...
init-exprk
loop:
terminal-expr1
...
if not terminal-exprn goto out
statement
bottom:
step-expr1
...,
step-exprm
goto loop
}
out:
Note:
- The loop parameter expressions are arbitrary expressions, except
that, in Java, the terminal expressions must be Boolean expressions.
- The loop parameter expressions are evaluated each time through the
loop, so changing variables that are part of them, does affect the
number of times the loop is executed.
- The number of times the loop is exected is controlled by the last
terminal expression, which is evaluated each time through the loop, so
assigning to any variables it uses inside the loop does affect the
number of times the loop is executed.
- If a
continue
statement is executed inside the loop,
control transfers to the bottom
label. The effect would
be to skip the rest of the loop body and continue with the next
iteration. (continue
may also be used inside while and
do-while loops.)
- The scope of any variable declared inside the init expressions is
limited to the loop parameter expressions and the loop body, and so
their last values are not available when the loop is terminated.
Just about every current language has a counter-controlled loop.
- Loops Through a Collection
- Counter-controlled loops usually use the counter, not for itself, but to
index the elements in some collection. Several languages do this more
directly.
Python doesn't have a counter-controlled loop. Its equivalent is
[Lutz, Python Pocket Reference, p. 30]
for target in sequence:
suite
[else:
suite]
The loop variable(s) range(s) over all the members of the given
sequence. The effect of the counter-controlled loop
is achieved by the range
function:
>>> range(5)
[0, 1, 2, 3, 4]
>>> range(2,6)
[2, 3, 4, 5]
>>> for i in range(2,6):
... print i
...
2
3
4
5
The else
suite is executed if the loop terminates
normally (other than by executing break
.
Python allows sequence
to be any object for
which an Iterator
object is defined.
Several other languages, including Common Lisp, Java, Perl, and Ruby, have
loops that range over all the elements of some data structure.
Here's a simple example from Common Lisp:
cl-user(4): (loop
for d in '(Sunday Monday Tuesday Wednesday Thursday Friday Saturday)
do (print d))
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
nil
Actually, Common Lisp's loop
is extremely flexible. See
the CSE202
course notes on iteration and the Common
Lisp HyperSpec Sect 6.
Java now also has a
for-each loop:
import java.util.*;
public class DataLoop {
public enum Month {January, February, March, April, May, June,
July, August, September, October, November, December}
public static void main(String[] args) {
HashSet<DataLoop.Month> thirtyDayMonths = new HashSet<DataLoop.Month>();
thirtyDayMonths.add(Month.September);
thirtyDayMonths.add(Month.April);
thirtyDayMonths.add(Month.June);
thirtyDayMonths.add(Month.November);
String output = "Thirty days hath: ";
for (Month m : thirtyDayMonths) {
output += m + " ";
}
System.out.println(output);
int[] eightPrimes = {2,3,5,7,11,13,17,19};
System.out.println();
System.out.println("Eight primes:");
for (int p : eightPrimes) {
System.out.println(p);
}
} // end of main()
} // DataLoop
---------------------------------------------------------
<wasat:Programs:2:262> /util/java/jdk1.5.0/bin/javac DataLoop.java
<wasat:Programs:2:263>/util/java/jdk1.5.0/bin/java DataLoop
Thirty days hath: September June April November
Eight primes:
2
3
5
7
11
13
17
19
Following Java, we'll call this kind of loop a for-each loop.
- List Comprehensions
- A useful operation is to create a list by applying a function to the
elements of one or more lists. Consider this Python code for making a list
of pairs taken from two other lists using a for-each loop.
>>> x = []
>>> for a in [1,2,3]:
... for b in ['a','b','c']:
... x.append((a,b))
...
>>> x
[(1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b'), (2, 'c'), (3, 'a'), (3, 'b'), (3, 'c')]
This can also be done by mapping a function over a list. For example, in
Common Lisp:
cl-user(8): (mapcar #'1+ '(1 2 3))
(2 3 4)
cl-user(9): (mapcar #'(lambda (x) (list x (* x x))) '(1 2 3))
((1 1) (2 4) (3 9))
cl-user(10): (mapcan #'(lambda (x) (list x (* x x))) '(1 2 3))
(1 1 2 4 3 9)
cl-user(11): (mapcan #'(lambda (a)
(mapcar #'(lambda (b) (list a b))
'(a b c)))
'(1 2 3))
((1 a) (1 b) (1 c) (2 a) (2 b) (2 c) (3 a) (3 b) (3 c))
List comprehensions do this with special syntax. List comprehensions are in
Erlang, Haskell, and Python. Here's a use of Haskell's list
comprehensions:
Prelude> [ (a,b) | a <- [1,2,3], b <- ['a','b','c'] ]
[(1,'a'),(1,'b'),(1,'c'),(2,'a'),(2,'b'),(2,'c'),(3,'a'),(3,'b'),(3,'c')]
Prelude> [x | x<-[2..10], even x]
[2,4,6,8,10]
Prelude> [(x,x*x*x) | x<-[2..10], even x]
[(2,8),(4,64),(6,216),(8,512),(10,1000)]
Look again at the Haskell version of the sieve of
Eratosthenes.
- Generators
- A generator, also called an "iterator", is a function that, each time it is
called, returns another member of some data structure (collection). There are
generally three parts to a generator function:
- A function that takes the collection as argument, and sets up
the generator. It may also return the generator function as its
value.
- The generator function itself, that returns another element of the
collection each time it is called.
- A way to tell that the collection has been exhausted. Either the
generator returns some special value (such as nil), or throws an
exception, or there is a special function for the purpose.
The use of generators is called external iteration, whereas internal
iteration is the "normal" iteration, such as the use of the for-each loop.
[Erich Gamma, Richard Helm, Ralph Johnson, & John M. Vlissides, Design
Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995,
as quoted in David Flanagan & Yukihiro Matsumoto, The Ruby Programming
Language, O'Reilly Media, 2008, p. 137.]
Generators in Java are classes that implement the
Iterator
interface. Their three parts are the three methods
iterator()
next()
hasNext()
Java iterators are used "under the covers" in the
for-each
loop.
Python's and Ruby's for
loops also use iterators "under the
covers". The two methods of Python's iterators are [Python Library Reference Sect. 2.3.5]:
__iter__()
: Returns an iterator object.
next()
: Returns the next item, or throws a
StopIteration
exception.
Here's a Common Lisp example of writing a generator, and using it to solve
the Same Fringe Problem:
(defun fringeGen (tree)
"Returns a generator function
that generates the fringe (sequence of leaves) of the given tree."
#'(lambda ()
(cond ((atom tree) (pop tree))
((atom (first tree)) (pop tree))
(t (loop until (atom (first tree))
do (setf tree (append (first tree) (rest tree))))
(pop tree)))))
(defun sameFringe (tree1 tree2)
"Returns t if the two trees, tree1 and tree2, have equal fringes;
otherwise returns nil."
(loop with gt1 = (fringeGen tree1)
with gt2 = (fringeGen tree2)
for l1 = (funcall gt1)
for l2 = (funcall gt2)
when (and (null l1) (null l2))
return t
unless (eql l1 l2)
return nil))
---------------------------------------------------------
cl-user(52): (setf nextLeaf (fringeGen '(((a) b) (c d) e)))
#<Closure (:internal fringeGen 0) @ #x71cb11aa>
cl-user(53): (loop for x = (funcall nextLeaf)
while x
do (print x))
a
b
c
d
e
nil
cl-user(54): (sameFringe '(a b c) '(a b c))
t
cl-user(55): (sameFringe '(a b c) '(a c))
nil
cl-user(56): (sameFringe '(a (b c)) '((a b) c))
t
cl-user(57): (sameFringe '(a (b c)) '((a b)))
nil
- Recursive Loops
- Loops are one of the three classes of control structures of the
Böhm/Jacopini theorem. The loops we have been considering have
been iterative loops. Recursive loops are an alternative. For
example, an iterative Lisp function to call the function
visit
on every member of a list is
(defun visitAll (list)
(loop for x in list
do (visit x)))
whereas a recursive version to do the same thing is
(defun visitAll (list)
(unless (endp list)
(visit (first list))
(visitAll (rest list))))
Every iterative loop may be rewritten as a recursive loop, but some
recursive loops may be rewritten as iterative loops only with the aid
of an explicit stack.
Common Lisp example of a recursive function that could only be written
iteratively with an explicit stack:
(defun polishPrefixEval (stack)
(when stack
(let ((op (pop stack)))
(cond ((numberp op)
(cons op (polishPrefixEval stack)))
((member op '(+ - * /))
(setf stack (polishPrefixEval stack))
(cons (funcall op (first stack) (second stack))
(nthcdr 2 stack)))
(t (error "*** Bad stack element ~s ***" op))))))
--------------------------------------------------------------------------------
cl-user(118): (trace polishPrefixEval)
cl-user(119): (polishPrefixEval '(+ * 2 3 4))
0[2]: (polishPrefixEval (+ * 2 3 4))
1[2]: (polishPrefixEval (* 2 3 4))
2[2]: (polishPrefixEval (2 3 4))
3[2]: (polishPrefixEval (3 4))
4[2]: (polishPrefixEval (4))
5[2]: (polishPrefixEval nil)
5[2]: returned nil
4[2]: returned (4)
3[2]: returned (3 4)
2[2]: returned (2 3 4)
1[2]: returned (6 4)
0[2]: returned (10)
(10)
cl-user(120): (polishPrefixEval '(* + 2 3 4))
0[2]: (polishPrefixEval (* + 2 3 4))
1[2]: (polishPrefixEval (+ 2 3 4))
2[2]: (polishPrefixEval (2 3 4))
3[2]: (polishPrefixEval (3 4))
4[2]: (polishPrefixEval (4))
5[2]: (polishPrefixEval nil)
5[2]: returned nil
4[2]: returned (4)
3[2]: returned (3 4)
2[2]: returned (2 3 4)
1[2]: returned (5 4)
0[2]: returned (20)
(20)
cl-user(121): (polishPrefixEval '(* 2 + 3 4))
0[2]: (polishPrefixEval (* 2 + 3 4))
1[2]: (polishPrefixEval (2 + 3 4))
2[2]: (polishPrefixEval (+ 3 4))
3[2]: (polishPrefixEval (3 4))
4[2]: (polishPrefixEval (4))
5[2]: (polishPrefixEval nil)
5[2]: returned nil
4[2]: returned (4)
3[2]: returned (3 4)
2[2]: returned (7)
1[2]: returned (2 7)
0[2]: returned (14)
(14)
cl-user(122): (polishPrefixEval '(** 2 + 3 4))
0[2]: (polishPrefixEval (** 2 + 3 4))
Error: *** Bad stack element ** ***
Restart actions (select using :continue):
0: Return to Top Level (an "abort" restart).
1: Abort entirely from this (lisp) process.
[1] cl-user(123): :res
0[2]: returned-by-throwing to tag top-level-reset: nil
Tail Recursion: The value of the recursive call is returned as is. Can be
turned into an iterative loop by a compiler. Compare:
(defun sumAllNotTR (list)
(if list
(+ (first list) (sumAllNotTR (rest list)))
0))
(defun sumAllTR (list &optional (value 0))
(if list
(sumAllTR (rest list) (+ (first list) value))
value))
--------------------------------------------------------
cl-user(137): (trace sumAllNotTR sumAllTR)
(sumAllTR sumAllNotTR)
cl-user(138): (sumAllNotTR '(1 2 3 4))
0[2]: (sumAllNotTR (1 2 3 4))
1[2]: (sumAllNotTR (2 3 4))
2[2]: (sumAllNotTR (3 4))
3[2]: (sumAllNotTR (4))
4[2]: (sumAllNotTR nil)
4[2]: returned 0
3[2]: returned 4
2[2]: returned 7
1[2]: returned 9
0[2]: returned 10
10
cl-user(139): (sumAllTR '(1 2 3 4))
0[2]: (sumAllTR (1 2 3 4))
1[2]: (sumAllTR (2 3 4) 1)
2[2]: (sumAllTR (3 4) 3)
3[2]: (sumAllTR (4) 6)
4[2]: (sumAllTR nil 10)
4[2]: returned 10
3[2]: returned 10
2[2]: returned 10
1[2]: returned 10
0[2]: returned 10
10
- Backtrack Control Structures
- Based on success/fail tests.
test1, test2, ..., testi, testi+1, ... testn
If testi succeeds, try testi+1
If testi fails, back up to testi-1, and try to succeed another way
If testn succeeds, done
If test1 fails ultimately, entire set fails.
Consider matching the pattern [abc]*abcbe
against the
string abcaabcbabccdabcbe
See XEmacs
21.5 HTML Manuals Sect. 12.4
Prolog uses backtracking as its main control structure:
birthday(arthur, "Dec 3, 1980").
birthday(bea, "March 15, 1985").
birthday(chuck, "Dec 3, 1980").
birthday(dave, "March 15, 1985").
birthday(ethel, "June 17, 1975").
birthday(fran, "March 15, 1985").
findSame :- birthday(X,D),
format("1: ~a's birthday is ~s.~n", [X,D]),
birthday(Y,D),
format("2: ~a's birthday is ~s.~n", [Y,D]),
Y \== X,
format(" ~a and ~a have the same birthday.~n", [X,Y]),
birthday(Z,D),
format("3: ~a's birthday is ~s.~n", [Z,D]),
Z \== Y, Z \== X,
format(" ~a, ~a, and ~a have the same birthday.~n", [X,Y,Z]).
-----------------------------------------------------------------------------
<wasat:Programs:2:273> sicstus -l birthdays.prolog --goal "findSame,halt."
...
1: arthur's birthday is Dec 3, 1980.
2: arthur's birthday is Dec 3, 1980.
2: chuck's birthday is Dec 3, 1980.
arthur and chuck have the same birthday.
3: arthur's birthday is Dec 3, 1980.
3: chuck's birthday is Dec 3, 1980.
1: bea's birthday is March 15, 1985.
2: bea's birthday is March 15, 1985.
2: dave's birthday is March 15, 1985.
bea and dave have the same birthday.
3: bea's birthday is March 15, 1985.
3: dave's birthday is March 15, 1985.
3: fran's birthday is March 15, 1985.
bea, dave, and fran have the same birthday.
- Dijkstra's guarded loop
do <test1> -> <statement1>
[] <test2> -> <statement2>
[] ...
[] <testn> -> <statementn>
od
Evaluate the tests, and, if any evaluate to true, nondeterministically
execute the statement of one of the tests that evaluates to true, and
then, do it all again. When none of the tests evaluates to true, the
loop terminates.