## Lecture 4: Notes

### for...in

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];

### records

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);

### random cards

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. ### files 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. #### text files 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. #### binary files 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. ### writing to and reading from a binary file 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? ');

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. ### analyzing a program’s running time 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. ### asymptotic notation 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) ≤ cg(n) whenever nn0 . 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 ### common time complexities 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 ### factoring integers 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: ');

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: ');

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 ji > 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).