The for…in
statement loops over
successive values of an array:
var a: array[1..100] of integer; sum: integer = 0; … for v in a do sum := sum + v;
The preceding for
loop is equivalent to
for i := low(a) to high(i) do sum := sum + a[i];
A record is a compound object that is composed of a set of fields. Each distinct record type defines the names and types of the fields that it contains. For example:
type book = record title: string; author: string; pages: integer; end;
You can access a record's fields using the '.'
operator. For example:
var b: book; begin b.title := 'war and peace'; b.author := 'leo tolstoy'; b.pages := 1440; … writeln('title = ', b.title);
You can initialize a variable of any record type as you declare it:
var b: book = (title: 'war and peace'; author: 'leo tolstoy'; pages: 1440);
Here is a program that uses a record type to represent a playing card:
card = record rank: 2 .. 14; // 11 = jack, 12 = queen, 13 = king, 14 = ace suit: suit; end;
The program uses an array of card
to
hold a hand of cards. It draws a random 5-card hand and checks
whether it is a flush, i.e. whether all suits in the hand are
identical.
{$r+} uses math, sysutils; type suit = (club, diamond, heart, spade); card = record rank: 2 .. 14; // 11 = jack, 12 = queen, 13 = king, 14 = ace suit: suit; end; hand = array of card; function describe(c: card): string; var ranks: array[11..14] of char = ('J', 'Q', 'K', 'A'); suits: array[suit] of string = ('♣', '♦', '♥', '♠'); s: string; begin if c.rank <= 10 then s := intToStr(c.rank) else s := ranks[c.rank]; describe := s + suits[c.suit]; end; function randomCard(): card; var c: card; begin c.rank := random(13) + 2; c.suit := suit(random(4)); randomCard := c; end; function isIn(c: card; h: hand): boolean; var i: integer; begin for i := low(h) to high(h) do if (c.rank = h[i].rank) and (c.suit = h[i].suit) then exit(true); exit(false); end; function draw(n: integer): hand; var h: hand; i: integer; c: card; begin setLength(h, 0); for i := 1 to n do begin repeat c := randomCard(); until not isIn(c, h); setLength(h, i); h[i - 1] := c; end; draw := h; end; function isFlush(h: hand): boolean; var i: integer; begin for i := low(h) to high(h) - 1 do if h[i].suit <> h[i + 1].suit then exit(false); exit(true); end; var h: hand; i: integer; begin randomize; h := draw(5); for i := 0 to high(h) do write(describe(h[i]), ' '); writeln; writeln(isFlush(h)); end.
In general, files hold either text or binary data. In a text file, all information is stored as Unicode characters. For example, an integer in a text file is stored as a series of characters representing decimal digits. In a binary file, an integer is stored as a series of bytes, each of which holds 8 consecutive bits of the integer’s binary representation.
The text
type represents a text file:
var novel: text;
Call the assign
function to assign a
filename to a file. You can then call reset
to open the file for reading, or rewrite
or append
to open the file for writing.
Be warned that rewrite
will truncate the
file, i.e. erase all its existing contents, if it already exists!
Call close
to close the file when you
are done reading or writing.
begin assign(novel, ‘great_american’); rewrite(novel); writeln(novel, ‘It was a dark and stormy night.’); writeln(novel, ‘The wind howled through the treetops, and there was no moon in sight.’); close(novel);
The read
, readln
,
write
and writeln
functions work with text files just as they do for standard input and
output.
The file
type represents a file on
disk containing a particular type of binary data:
var f: file of integer; g: file of real;
Often a file will hold a compound type:
type time = record year, month, day: integer; hour, minute, second: integer; end; var timestamps: file of time;
Note, however, that a file
cannot
contain data of any of these types:
string
dynamic arrays
any compound data type containing the above types
If you want to store character data in a binary file, you must use
a fixed-size character array, e.g. an array[1..100]
of char
.
The assign
, reset
,
rewrite
, append
and close
functions work with binary
files just like with text files. The read
and write
functions also work with
binary files, but you can only read or write values of the binary
file’s element type. Furthermore, readln
and writeln
don’t work with binary
files since there are no newlines in such files.
Here is a program that simulates a random walk and writes a series of points to a binary file.
{$mode delphi} type point = record x: integer; y: integer; end; var f: file of point; n, i: integer; p: point; begin randomize; write('iterations? '); readln(n); assign(f, 'walk_data'); rewrite(f); p.x := 0; p.y := 0; for i := 1 to n do begin write(f, p); case random(4) of 0: p.x := p.x - 1; 1: p.x := p.x + 1; 2: p.y := p.y - 1; 3: p.y := p.y + 1; end; end; close(f); end.
Here is a complementary program that reads the data written by the previous program, and prints out some statistics about the random walk.
{$mode delphi} uses math; type point = record x: integer; y: integer; end; var f: file of point; time: integer = 0; p: point; distance: integer; furthest: integer = 0; furthest_time: integer = 0; begin assign(f, 'walk_data'); reset(f); while not eof(f) do begin read(f, p); time := time + 1; if (p.x = 0) and (p.y = 0) then writeln('hit origin at time ', time); distance := abs(p.x) + abs(p.y); if distance > furthest then begin furthest := distance; furthest_time := time; end; end; close(f); writeln('total time = ', time); writeln('furthest distance = ', furthest, ' at time ', furthest_time); end.
We are generally interested in characterizing a program or function’s running time as a function of its input size n.
Consider the following function, which returns the largest column sum in an (n x n) matrix.
uses math; type matrix = array of array of integer; // Return the largest column sum in an (n x n) matrix. function largestColumnSum(a: matrix; n: integer): integer; var sum, largest, row, col: integer; begin largest := - MaxInt; // c1 for col := 0 to n - 1 do begin sum := 0; // c2 for row := 0 to n - 1 do sum := sum + a[row, col]; // c3 largest := max(largest, sum); // c4 end; exit(largest); // c5 end;
For the statements labelled c1
through
c5
, let c1,
…c5
be the time costs of running each of these statements once. We assume
that the ci
are
constant.
For a given value of n,
c3
will run n2
times
c2
and c4
will run n times
c1
and
c5
will
run once
So the total running time of these lines is c3n2 + (c2 + c4)n + (c1 + c5). This equals an2 + bn + d for constants a, b, d.
Other statements in this function (e.g. the for loops themselves) will also each execute either n2 times, n times or 1 time. When we include their overhead, the constants a, b and d may change, but the function’s running time remains of the form an2 + bn + d.
In computer science, we generally ignore the constant factors and lower-order terms in a running time such as an2 + bn + d, and write it instead using big-O notation: an2 + bn + d = O(n^2). This indicates the order of growth of the running time, also known as the time complexity of the function or program.
f(n) = O(g(n)) means that f(n) is eventually bounded by some constant factor times g(n). We say that g(n) is an asymptotic upper bound for f(n).
More formally:
Definition. f(n) = O(g(n)) if there exist constants c, n0 > 0 such that 0 ≤ f(n) ≤ c ⋅ g(n) whenever n ≥ n0 .
For given functions f and g, it can be awkward to show that f(n) = O(g(n)) using the definition above. It is often much easier to work with limits, so the following theorem is useful.
Definition. A function f is eventually positive if there exists some n0 such that f(n) > 0 whenever n > n0 .
Theorem. If functions f and g are eventually positive, then f(n) = O(g(n)) iff `underset(n→oo)("lim sup ") f(n)/g(n) < oo`.
For any function h, if `lim_(n→oo) h(n) = k`, then `underset(n→oo)("lim sup ") h(n)` must equal k as well. Combining this observation with the preceding theorem, we see that we can prove that f(n) = O(g(n)) by showing that f(n) / g(n) converges to a constant.
For example, let us show that an2 + bn + d = O(n2) as stated above. We have
`lim_(n→oo)((a n^2 + b n + d) / n^2) = lim_(n→oo)(a + b / n + d / n^2) = a + lim_(n→oo)(b / n + d / n^2) = a`
The functions below are in increasing order of asymptotic complexity.
notation |
name |
---|---|
O(1) |
constant |
O(log n) |
logarithmic |
O(n) |
linear |
O(n log n) |
log linear |
O(n2) |
quadratic |
O(n3) |
cubic |
O(nk) |
polynomial |
O(kn) |
exponential |
O(n!) |
factorial |
The Fundamental Theorem of Arithmetic states that each integer has a unique factorization as a product of prime numbers.
The simplest algorithm for factoring an integer is called trial division, in which we attempt to divide an integer n by successive integers i < n. Here is a program that performs naive trial division:
{$mode delphi} var n: integer; i: integer; begin write('number to factor: '); readln(n); i := 2; while i < n do if n mod i = 0 then begin write(i, ' '); n := n div i; end else i := i + 1; writeln(n); end.
This program works, but runs in time O(n) which is very inefficient for large n. We can make a couple of small changes to the program to make it more efficient:
{$mode delphi} var n: integer; i: integer; begin write('number to factor: '); readln(n); i := 2; while i <= sqrt(n) do if n mod i = 0 then begin write(i, ' '); n := n div i; end else if i = 2 then i := 3 else i := i + 2; writeln(n); end.
The most important change here is that we loop only while i ≤ `sqrt(n)`. To see that this is sound, consider the following loop invariant: No prime factors of n are less than i.
The invariant is certainly true at the beginning of the loop, since there are no primes less than 2. In each loop iteration, if i | n then we replace n by n / i, which must also have no prime factors less than i. If i does not divide n, then we advance i to the next odd integer, and n still has no prime factors that are less than i.
So the loop preserves the invariant. When the loop terminates, i > `sqrt(n)`. Since n is not divisible by any j < i, it must also not be divisible by any j ≥ i > `sqrt(n)`, for then n would be divisible by n / j < `sqrt(n)`, a contradiction. So the final value of n must be prime.
This modified algorithm runs in time O(`sqrt(n)`). The modification to advance i only through odd integers makes the algorithm run twice as fast (which does not change its time complexity).