In this lecture, we look at an algorithm to find primes which you might have learned in school. It is called the Sieve of Eratosthenes. The basic idea is the following.
Enumerate all positive integers starting from 2.
Forever do the following
Take the smallest of the remaining uncrossed integer say and circle it.
Cross out all numbers that are the factors of the circled integer .
All the circled integers are the list of primes. For an animated description see the wiki link http://en.wikipedia.org/Sieve_of_Eratosthenes
.
We want to convert this algorithm to haskell code. Notices that the Sieve seperates the primes from the composites. The primes are those that are circled and composites are those that are crossed. So let us start by defining
1 |
|
i.e. the primes are precisely the list of circled integers.
An integer gets circled if and only if it is not crossed at any stage of the sieving process. Furthermore, a integer gets crossed in the stage when its least prime factor is circled. So to check whether an integer is crossed all we need to do is to check whether there is a prime which divides it.
1 |
|
The function isCircle x
checks whether x
will eventually be circled. One need not go over all primes as the smallest non-trivial prime that divides a composite number should always be less than or equal to . This explains the first guard statement of the check function.
We now explain another feature of Haskell namely guards. Look at the definition of the function check
. Recall that a function is defined by giving a set of equations. For each such equation, we can have a set of guards. The syntax of these guarded equation looks like
f p_1 ... p_n | g_1 = e_1
| g_2 = e_2
| g_3 = e_3
| ...
| g_m = e_m
Each of the guards g_i
is a boolean expression. You should read this as "if f
's arguments match the patterns p1 ... pn
then its value is e_1
if g_1
is true, e_2
if g_2
is true, etc e_m
if g_m
is true". If multiple guards are true then the guard listed first has priority. For example consider the following function
1 |
|
Then f 0
is the string "non-negative"
. If you want to add a default guard, then use the keyword otherwise
. The keyword otherwise
is nothing but the boolean value True
. However, in guards it is a convention to write otherwise
instead of True
.
Finally, we want to define the list of circledInteger
. Clearly the first integer to be circled is 2. The rest are those integers on which the function isCircled
returns true. And here is the Haskell code.
1 |
|
Here filter is a function that does what you expect it to do. Its first argument is a predicate, i.e it take an element and returns a boolean, and its second argument is a list. It returns all those elements in the list that satisfies the predicate. The type of filter is given by filter :: (a -> Bool) -> [a] -> [a]
. This completes the program for sieving primes. Now load the program into the interperter
1 |
|
The take n xs
returns the first n
elements of the list xs
.
One thing you might have noticed is that the list primes
has a circular definition. The compiler is able to grok this circularity due to the fact that Haskell is a lazy language. No expression is evaluated unless it is required. For each integer in the list primes
to decide that it is circled, we need to consider only the primes that are less than its square root. As a result the definition of primes does not blow up into an infinitary computation.
We now explain another feature of a Haskell. Notice the use of mod
in the expression n `mod` x == 0
in the definition of the function divides
and in the guard x `divides` n
in the definition of the function check
. We have used a two argument function (i.e. its type is a -> b -> c
) like an operator. For any such function foo
we can convert it into a binary operator by back quoting it, i.e. `foo`
.
The converse is also possible, i.e. we can convert an operator into the corresponding function by enclosing it in a bracket. For example the expression (+)
(there is no space in side the bracket otherwise it is an error) is a function Int -> Int -> Int
.
1 |
|
The two function incr1
and incr2
both does the same thing; increments all the elements in a list of integers by 1.