Introduction

Descriptions of problems in the real world have many superfluous details.
An essential step in problem solving is to identify the
underlying abstract problem devoid of all unnecessary detail.
Similarly, a particular model of computer has many
details that are irrelevant to the problem,
for example the processor architecture and the word length.
One of the arts of computer programming is to suppress
unnecessary detail of the problem and of the computer used.
An invaluable aid is the use of a high level programming language such as
Algol, C, Haskell, Java, ML, Turing, etc..

Once problems are abstracted, it becomes apparent
that seemingly different problems are essentially similar
or even equivalent in a deep sense.
For example, the problems of maintaining a list
of students taking a lecture course
and of organising a dictionary structure in a compiler
have much in common;
both require the storage and manipulation of named things
and the things have certain attributes or properties.
Abstraction allows common solutions to seemingly different problems.
By using abstract algorithms and data structures,
a solution to a problem has maximum utility and scope for reuse.

Algorithms and data structures can be specified in any adequately
precise language.
English and other natural languages are satisfactory if used
with care to avoid ambiguity but more precise
mathematical languages and programming languages are generally preferred.
The execution of the latter can also be automated.
A program is the expression of some algorithm(s) and data structure(s)
in a particular programming language.
The program can be used on all types of computer for
which suitable language compilers or interpreters exist,
making it more valuable.

Sometimes details of a machine do intrude.
The word size of the computer may be relevant if very big numbers are
to be manipulated.
The relative speed of real and integer arithmetic may be critical
in simulating a physical system.
There may be a trade-off between the speed of a computation and the space
it uses.
However such details should only be allowed to intrude if absolutely necessary.

Given a problem to solve,
we select or design abstract data structures to store the data
and algorithms to manipulate them.
A data structure is an instance of a data type.
The design step is largely independent of any programming language.
The coding or programming step is to implement the abstract data structures
and algorithms in a particular programming language such as Turing.
An abstract data type defines certain static properties of
the data and certain dynamic operations on them.
It defines what the operations are, not how they work.
An algorithm defines some process, how an operation works.
It is specified in terms of simple steps.
Each step must be clearly implementable in a mechanical way,
perhaps by a single machine instruction,
by a single programming language statement or
by a previously defined algorithm.
An algorithm to solve a problem must be correct;
it must produce correct output when given valid input data.
An algorithm must terminate when given valid input data.
When the input data is invalid we usually want the
algorithm to warn us and terminate gracefully.
Often we want to know how much time an algorithm will take
or how much space (computer memory) it will use
and this leads to the analysis of algorithms
and to the field of computational complexity.

Example: The Abstract Data Type Int

Mathematicians have long studied the integers and their properties.
The consensus as to what integer-ness is
amounts to a particular collection of axioms
or rules that must be obeyed.
The way in which integers are represented is unimportant
provided only that all readers understand the notation -
binary, octal, decimal, hexadecimal, twos complement,
ones complement or sign and magnitude,
the choice does not matter.
What does matter is the way operations on integers behave.
The rules define this behaviour,
for example addition is commutative and zero is its identity value.
Unary plus and minus take an integer and return an integer,
denoted int->int.
A binary operator like plus takes two integers and returns an integer,
denoted int2->int.

These rules define an ideal for a programming language to
attempt to live up to.
In practice, a language can only approximate this ideal.
For example,
many languages
place a limit on the size of integers that can be represented
in the type int; it is equivalent to:

type int = minint..maxint

Invariably this range is determined by the word size
and for a typical 32 bit word and twos complement representation,
maxint=231-1 and
minint=-231.
While the programmer must be aware of this fact
it rarely causes difficulty.
If it is necessary to manipulate integers outside this range,
the programmer must implement the operations for his or her self.

Long-multiplication,
the usual manual method for multiplying large integers
is an example of an algorithm.
If the two integers have d1 and d2 digits respectively,
the algorithm requires approximately d1×d2 steps.

Pascal and
Turing provide sets where the element type is an index type -
int or enumerated types.
The reason is that these are easily and efficiently implemented as bit-maps.
It is not possible to have a set of alphanumeric words for example
and of course each set must be finite.

.so set.days.t

The sieve of Eratosthenes
is an algorithm that is over two thousand years old.
It is used to calculate prime numbers between 2 and n.
The algorithm uses a set of integers.
Initially the set contains all integers between 2 and n.
2 is the
first prime number and all multiples of 2 are removed from the set.
The next prime number, 3,
is found and all of its multiples are removed.
This process of finding the next prime and removing its multiples is repeated
until only prime numbers are left in the set.
The primes can then be printed.

Note that the algorithm was expressed in English.
Each step is sufficiently simple that it is
obvious how to carry it out without giving the matter
very much thought.
More precision is needed to express the algorithm in a programming language.
The set data type is provided in some programming languages.
The repetitive nature of the algorithm is implemented by loops
or recursion:

Exercises

Here is an informal description of an algorithm "by example":
If you know how to subtract and multiply small digits, eg. 2 and 3,
you can use the algorithm to multiply large digits, eg. 7 and 8, as follows.
Write down 7 and 8 side by side.
Write 10-7=3 under the 7 and 10-8=2 under the 8.
You should now have:

7 8
3 2

Take a diagonal pair (7 and 2 or 8 and 3 it does not matter).
Subtract them, getting 5 and write it down.
Now multiply the bottom two digits 2*3=6 and write the 6 after the 5.
That is the answer: 7*8=56.

7 8
3 2 7-2=5; 3*2=6; Ans=56

Use the method to compute 6*9.
Why does it work?
Does it always work?
Write down a formal
and general description of the algorithm in English.
Can it be generalised to multiply larger numbers, eg. 89*97?

Give a piece of code to calculate the median, or middle value,
of three distinct numbers x, y and z.

Implement large d-digit decimal integers as array 1..d of 0..9.
Write routines to do input, output, +, - for this implementation.