Programming I, 2018-9
Lecture 4 – Notes

text files

The text type represents a text file:

var
  f: text;

To write to a file, first call the assign function to assign a filename to a file, then call rewrite to open the file for writing. You can then write to the file using the familiar write and writeln functions. When you are done writing, call close to close the file.

Here's a program that writes the numbers from 1 to 10 to a file:

var
  f: text;
  i: integer;

begin
  assign(f, 'numbers.txt');
  rewrite(f);
  
  for i := 1 to 10 do
    writeln(f, i);
    
  close(f);
end.

Be warned that rewrite will truncate a file, i.e. erase all its existing contents, if it already exists!

Reading a file is similar, but you call reset to open a file for reading. This program adds a series of numbers read from a file:

var
  f: text;
  i: integer;
  sum: integer = 0;

begin
  assign(f, 'numbers.txt');
  reset(f);
  
  while not seekEof(f) do
    begin
      readln(f, i);
      sum := sum + i;
    end;
  writeln(sum);

  close(f);
end.

All of these file-related functions are listed in the library quick reference.

for...in

The for…in statement loops over successive values in an array:

var
  a: array[1..100] of integer;
  v: 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];

multidimensional arrays

We have seen that Pascal lets us declare an array of any type: an array of integer, an array of boolean, and so on. We can even declare an array of arrays, otherwise known as a multidimensional array.

Like a single-dimensional array, a multidimensional array may be either static or dynamic. You can declare a static multidimensional array like this:

  a: array[1..10] of array[1..20] of integer;

or

  a: array[1..10, 1..20] of integer;

The preceding two definitions are equivalent; you can use whichever syntax you like better.

The array we have just declared is a 2-dimensional table of dimensions 10 x 20, holding a total of 200 integer values.

You can access a particular array element using the syntax a[4][5] or a[4, 5]. These are completely equivalent and you can use whichever syntax you prefer.

Given the array a declared above, length(a) returns 10. That's because a actually is an array with 10 elements, each of which happens to be a 20-element subarray. length(a[1]) returns 20. (The number 1 is arbitrary here; you could just as easily say length(a[3]), for example.)

We will have many uses for multidimensional arrays. A multidimensional array can be used to store a matrix, for example, or a raster image (with each pixel in a single element).

Here's how to declare a dynamic multidimensional array:

var
  d: array of array of integer;

To set the dimensions of such an array, call setLength and pass each dimension as a separate parameter:

setLength(d, 100, 20);

Here is a program that reads an integer N from standard input, followed by an N x N matrix of integers. The program writes a message indicating whether the matrix is symmetric. (Recall that an N x N matrix A is symmetric iff Aij = Aji for all 1 ≤ i, j ≤ N.)

var
  n, i, j: integer;
  a: array of array of integer;

begin
  readln(n);
  setlength(a, n, n);
  
  for i := 0 to n - 1 do
    for j := 0 to n - 1 do
      read(a[i, j]);
  
  for i := 1 to n - 1 do
    for j := 0 to i - 1 do
      if a[i, j] <> a[j, i] then
        begin
          writeln('asymmetric');
          exit;
        end;
  
  writeln('symmetric');
end.

Note carefully the form of the second double loop above. We iterate only over values (i, j) below the diagonal of the matrix:

  for i := 1 to n - 1 do
    for j := 0 to i - 1 do

If we had instead written

  for i := 0 to n - 1 do
    for j := 0 to n - 1 do

then our program would still work correctly, but would be less efficient because it would check redundant pairs. For example, when i = 3 and j = 7 it would test whether a[3, 7] <> a[7, 3]. Then when i = 7 and j = 3 it would test whether a[7, 3] <> a[3, 7], which is redundant.

functions and procedures

We have already learned how to call functions and procedures in Pascal's standard library. We will now learn how to write our own functions and procedures. This will allow us to write larger and more interesting programs than before.

Recall that in Pascal a function returns a value but a procedure does not.

Here is a procedure that draws a rectangle of asterisks with a given height and width:

procedure rectangle(height, width: integer);

var
  i: integer;

begin
  for i := 1 to height do
    writeln(stringOfChar('*', width));
end;

Here, height and width are parameters to the procedure. i is a local variable.

The first line containing the procedure's name and parameters is its declaration. Actually we could have written this line in either of two ways:

procedure rectangle(height, width: integer);

or

procedure rectangle(height: integer; width: integer);

These forms are equivalent.

We can call the rectangle procedure from our main begin/end block like this:

begin
  rectangle(5, 8);
end.

The values 5 and 8 are the arguments that are passed to the procedure. The program's output is

********
********
********
********
********

factorial function

Recall that is N is a positive integer, then N! ("N factorial") is defined as

N! = N · (N – 1) · … · 3 · 2 · 1

Here is a function that computes the factorial of a given number N:

function factorial(n: integer): integer;

var
  i: integer;
  f: integer = 1;

begin
  for i := 2 to n do
    f := f * i;
  exit(f);
end;

The return type of this function is integer, and is written after the colon (':') in the function's declaration.

Every function must return a value. In Pascal there are two ways to accomplish this. You may either use the exit statement as in the function above, or you can assign to the function's name:

factorial := f;

The exit statement immediately exits the function, even if you are in the middle of a loop. (Assigning to the function's name does not immediately exit.)

In this course we will generally use exit for returning function values.

global and local variables

Consider this program:

var
  x: integer;
  
function abc(a: integer): integer;

var
  i: integer;
  
begin
  i := a + x;
  exit(i);
end;

var
  y: integer;

begin
  x := 3;
  y := 4;
  writeln(abc(y));
end.

The variable x declared at the top of the program is a global variable. It is visible everywhere in the program. The funcction abc and the main begin/end block can both read and modify x.

The variable i is local to the function abc. Code outside abc cannot see this variable.

The variable y is local to the main begin/end block. It is not visible inside the function abc.

In general you should avoid global variables when possible. Global variables make a program harder to understand, since it may not be obvious which functions in a program might change them. (Global constants, however, are fine.)

passing by value and by reference

Consider this program:

procedure increment(i: integer);

begin
  i := i + 1;
  writeln(i);
end;

var
  j: integer = 7;

begin
  increment(j);
  writeln(j);
end.

The program will print

8

7

Here is why. First the procedure increment receives the value i = 7. The statement i := i + 1 sets i to 8, and increment writes this value.

Now control returns to the main begin/end block. The value of j is still 7! That's because by default Pascal passes arguments by value: a function or procedure receives a local copy of the value that was passed. A function may modify its local copy, but that does not change the corresponding value in the caller, i.e. the code that called the function. And so the second number printed by this program is 7.

To change this behavior, you can write var before any parameter in a function or procedure declaration. Let's modify the first line of the program above to be

procedure increment(var i: integer);

The var keyword indicates that an argument is to be passed by reference. In this case, the procedure does not receive a local copy – instead, it receives a reference to the caller's value. In this example, i is a reference to the value of j. If the procedure changes i, then j will also change. And so the program will print

8

8

Let's now write a procedure swap that exchanges the values of two variables:

procedure swap(var i, j: integer);

var
  t: integer;

begin
  t := i;
  i := j;
  j := t;
end;

We can call this procedure in our main begin/end block as follows:

var
  x: integer = 4;
  y: integer = 6;

begin
  swap(x, y);
  writeln(x, ' ', y);
end.

The program will print

6 4

Notice that in the declaration of swap we must use the var keyword. Without it, the procedure would not be able to modify the variables in the caller.

writing good code

Functions are an essential tool for writing larger programs in a way that is easy to understand. Here are three general rules to follow for writing good code with the help of functions:

  1. Don't repeat yourself. Beginning programmers often write code that looks like this:

if x > 0 then
  begin
    ... 50 lines of code ...
  end
else
  begin
    ... 50 very similar lines of code ...
  end

    This is bad code. It is hard to read: the differences between the 50-line blocks may be important, but hard to spot. And it is hard to maintain. Every time you change one of the parallel blocks of code, you must change the other. That is a bother, and it's also easy to forget to do that.

    In this situation you should factor out the 50 lines of code into a separate function. If there are differences between the 50-line blocks, you can have function parameter(s) that allow the function to behave in either fashion.

  1. Every function, including the main begin/end block, should fit on a single screen. Practically speaking this means that functions should generally be limited to about 50 lines. Functions that are much longer than this quickly become hard to understand.

  2. Make variables as local as possible. In other words, avoid global variables in Pascal. (In many programming languages you can declare variables inside a loop body or other block of code inside a function, which is generally a good practice. But unfortunately Pascal does not allow this.)

array assignment

Consider the following program:

var
  a: array[1..10] of integer;
  b: array[1..10] of integer;
  i: integer;

begin
  for i := 1 to 10 do
    a[i] := i * i;
    
  b := a;
  a[1] := 77;
  writeln(b[1]);
end.

In this program, a and b are static arrays of integers. The assignment b := a copies the entire array a into b. The following assignment a[1] := 77 changes the value of a[1] but has no effect on b, which is a separate array. The program prints

1

Here is a similar-looking program:

var
  a: array of integer;
  b: array of integer;
  i: integer;

begin
  setLength(a, 10);
  for i := 0 to 9 do
    a[i] := i * i;
    
  b := a;
  a[0] := 77;
  writeln(b[0]);
end.

In this program, a and b are variables of dynamic array type. Assignment between these variables has a different behavior than in the static case above. The assignment b := a does not copy the array a. Instead, it modifies the variable b to point to the same array as a. So after the assignment a[0] := 77 the value of b[0] is also 77, since it refers to the same element of the same array. The program prints 77.

We will not so often use array assignment, but it is good to be aware of this behavior difference. (When we study pointers and linked data structures in a few weeks, we'll see that pointer assignment works similarly to dynamic array assignment as described here.)

passing arrays to functions

Here is a program that passes a dynamic array to a procedure:

procedure abc(a: array of integer);

begin
  a[0] := a[1] + a[2];
end;

var
  b: array of integer;
  i: integer;

begin
  setLength(b, 10);
  for i := 0 to 9 do
    b[i] := i;
    
  abc(b);
  
  writeln(b[0]);
end.

Because we did not specify the var keyword, the array is passed by value. So the procedure receives a local copy of the array. The assignment to a[0] inside the procedure changes the local copy, but the caller's array b is unaffected. The program prints 0.

If we change the first line to

procedure abc(var a: array of integer);

now the array will be passed by reference. Then the parameter a will refer to the same array as b, and the assignment to a[0] will affect b[0]. The program will print 3.

(The situation would be similar if we were passing a static array. But passing static arrays to functions involves some syntactic surprises, which we will discuss in the next lecture.)

bubble sort

Sorting is a fundamental task in computer science. Sorting an array means rearranging its elements so that they are in order from left to right.

We will study a variety of sorting algorithms in this class. Most of these algorithms will work on sequences of any ordered data type: integer, real, string and so on.

Our first sorting algorithm is bubble sort. Bubble sort is not terribly efficient, but it is simple to write. It can be a reasonable choice if you are only sorting a small number of values.

Bubble sort works by making a number of passes over the input. On each pass, it compares pairs of elements: first elements 1 and 2, then elements 2 and 3, and so on. After each comparison, it swaps the elements if they are out of order.

For example, consider bubble sorting this array:

%3

On the first pass, the algorithm first compares 6 and 5, and swaps them because 5 < 6:

%3

Now the algorithm compares 6 and 3, and swaps them because 3 < 6:

%3

And so on. At the end of the first pass, the array looks like this:

%3

Notice that the largest element (8) has moved to the last position. If input array has N elements, then the first pass of a bubble sort makes N – 1 comparisons, and always brings the largest element into the last position. So the second pass does not need to go as far: it makes only N – 2 comparisons, and brings the second-largest element into the second-to-last position. And so on. After N – 1 passes, the sort is complete and the array is in order.

Here is an animation of bubble sort in action on the above array.

Here is Pascal code implementing a bubble sort:

procedure swap(var a, b: integer);
var
  t: integer;
begin
  t := a;
  a := b;
  b := t;
end;

procedure bubbleSort(var a: array of integer);
var
  i, j: integer;
begin
  for i := high(a) - 1 downto 0 do
    for j := 0 to i do
      if a[j] > a[j + 1] then swap(a[j], a[j + 1]);
end;