To review the elements of Pascal that we learned in this lecture (enumerated types, procedures, functions, recursions, variable scope), read the following sections of our Pascal Made Simple textbook:
in chapter 3: “Defining types”
chapter 4 (all sections)
In this lecture we mentioned bases other than 10, and one of our homework exercises uses them as well. If you are not familiar with writing numbers in different bases, please read this page:
At the end of the lecture I reviewed some basic properties of logarithms. We will be using logarithms a lot in this course. If you need to review them, you can do so here:
We will also soon be using limits, especially limits at infinity. If you need to review your knowledge of limits, please read these pages:
Here are a few more notes on various topics.
Remember that all dynamic arrays have indices starting with 0! If you call setLength(a, 100), you will end up with an array with indices from 0 to 99. A write to a[100] writes to unallocated memory, which could corrupt other program variables or cause a crash.
To guard against this, you can use the {$r+}
directive, which enables range checking. This has two consequences:
On every array access a[i], the runtime checks that the index i is within the bounds of the array a. If it isn't, the program will terminate with an error.
On every assignment to a variable whose type is a range
(e.g. var x: 5 .. 15
), the runtime checks that the
value being assigned is in the range. If it isn't, the program will
terminate with an error.
I recommend that you put this directive at the top of every program that uses arrays.
This program prints five random cards, using an enumerated type to represent suits:
type suit = (club, diamond, heart, spade); var names: array[11..14] of string = ('jack', 'queen', 'king', 'ace'); rank: 2..14; s: suit; i: integer; begin randomize; for i := 1 to 5 do begin rank := random(13) + 2; s := suit(random(4)); case rank of 2..10: write(rank); else write(names[rank]); end; writeln(' of ', s, 's'); end; end.
Certain types are considered to be ordinal types: these
include boolean
, all integer types, char
,
and enumerated types. Values of ordinal types can be converted
to/from integers. They can be used
with comparison operators such as < and >
as array indices
in case
statements
as for
loop variables
with the in
operator, which is used as
follows:
if c in ['A'..'Z'] then …
The exit
statement immediately exits the nearest
enclosing procedure or function:
exit;
If there is no enclosing procedure or function, the program exits.
In a function, exit can take a parameter indicating a value to be returned from the function:
exit(4);
A function can call itself; this is called recursion. Here is the factorial function, written recursively:
function factorial(n: integer): integer; begin if n = 0 then factorial := 1 else factorial := n * factorial(n - 1); end;
We will take a deeper look at solving problems using recursion a bit later on in this course.
The last digit in the decimal number d is d mod 10
.
All digits except the last are d div 10
. To retrieve
digits in any other base b, use b instead of 10.
This program adds up all the decimal digits in a number:
var n: integer; digits: integer = 0; begin readln(n); while n > 0 do begin digits := digits + (n mod 10); n := n div 10; end; writeln(digits); end.
As we saw in the lecture 1, the ord
function converts
a character to its ASCII value, and the corresponding function chr
converts an ASCII value to a character.
The ASCII code for ‘0’ is not zero, so if c is a character
holding a digit, you cannot convert it to the corresponding numeric
value by simply calling the ord
function. Instead, you
need to subtract the value of ord('0')
:
d := ord(c) – ord('0');
Similarly, to convert a numeric value d
to the
corresponding digit character:
c := chr(ord('0') + d);
This program converts a number to binary (base 2) by retrieving one digit at a time and prepending the digits to a string:
var n: integer; s: string = ''; begin readln(n); repeat s := chr(ord('0') + (n mod 2)) + s; n := n div 2; until n = 0; writeln(s); end.
The function below parses an integer n from a string s (just like
the library function strToInt
). It generates the integer
using a technique known as Horner’s method, which works as
follows. We use a variable n to store the value of the digits we have
seen so far, initializing n to 0. We pass over the digits in the
string from left to right, and each time we see a new digit, we
multiply n by 10 (the decimal base) and add the value of the new
digit.
For example, if the string s = “347”, the number n is generated as follows:
0 * 10 + 3 = 3
3 * 10 + 4 = 34
34 * 10 + 7 = 347
Here is the code:
function toInt(s: string): integer; var n: integer = 0; i: integer; begin for i := 1 to length(s) do n := n * 10 + ord(s[i]) - ord('0'); toInt := n; end;
In a sorted array of length n, all elements are in order:
a[0] <= a[1] <= a[2] <= … <= a[n]
Consider the problem of searching for an integer (called the “key”) in a sorted array a. Here is a naive sequential search:
function iterative_search(a: array of integer; key: integer): boolean; var i: integer; begin for i := low(a) to high(a) do if a[i] = key then exit(true); exit(false); end;
A binary search will perform much better:
function binary_search(a: array of integer; key: integer): boolean; var lo, hi, mid: integer; begin lo := low(a); hi := high(a); while lo <= hi do begin mid := (lo + hi) div 2; if a[mid] = key then exit(true); if a[mid] < key then lo := mid + 1 else // a[mid] > key hi := mid - 1; end; exit(false); end;
In a binary search, we use two variables lo and hi to keep track of the range of indices where the key might be found. To see how this function works, consider the following compound condition:
For all j < lo, a[j] < key
For all j > hi, a[j] > key
This condition is called a loop invariant. It is not written in code; it is a mathematical statement that helps us show that the program is correct. The invariant is true at the beginning, and remains true after each loop iteration:
At the beginning, there are no array elements a[j] with j < lo or j > hi, so the condition is certainly true.
If a[mid] < key, then we know that a[j] < key for any j ≤ mid. So we set lo := mid + 1, and subcondition (1) remains true.
Similarly, if a[mid] > key, then we know that a[j] > key for any j ≥ mid. So we set hi := mid - 1, and subcondition (2) remains true.
Whenever lo ≤ hi we keep looping, since we might have a[j] = key for some j in the range lo ≤ j ≤ hi. Eventually the loop will terminate: in particular, if lo = hi then the loop will either set lo to hi + 1 or will set hi to lo – 1, and in either case then lo > hi and the loop exits. At that point we can return false, since for all j either j < lo or j > hi, so by the loop invariant we have a[j] <> key for all j.