Tonλ's blog May the λ be with you

Programming in haskell - ch5 - Lists comprehension

by @ardumont on

List comprehension

Using a list comprehension, give an expression that calculates the sum
12 + 22 + . . . 1002.

let ss100 = sum [ x^2 | x <- [1..100]]

replicate

In a similar way to the function length, show how the library function
replicate :: Int → a → [ a ] that produces a list of identical elements can be defined using a list comprehension.

For example: > replicate 3 True

[True, True, True]

It's like generating n times the same input.

replicate :: Int -> a -> [a]
replicate n x = [x | _ <- [1..n]]

Here are some examples:

*Ch5> replicate 10 'a'
"aaaaaaaaaa"
*Ch5> replicate 10 1
[1,1,1,1,1,1,1,1,1,1]

pyths

A triple (x, y, z) of positive integers is pythagorean if x2 + y2 = z2.

Using a list comprehension, define a function pyths :: Int → [(Int, Int, Int)] that returns the list of all pythagorean triples whose components are at most a given limit.

For example: > pyths 10

[(3, 4, 5), (4, 3, 5), (6, 8, 10), (8, 6, 10)]

We search all the triplets (x, y, z) such as (x,y,z) in [1..n]3 and x2+y2=z2:

pyths :: Int -> [(Int, Int, Int)]
pyths n = [(x, y, z) | x <- m,
                       y <- m,
                       z <- m,
                       x^2 + y^2 == z^2 ]
          where m = [1..n]

perfect numbers

A positive integer is perfect if it equals the sum of its factors, excluding the number itself.

Using a list comprehension and the function factors, define a function
perfects :: Int → [ Int ] that returns the list of all perfect numbers up to a given limit.

For example: > perfects 500

[6, 28, 496]

We are first given the definition for a perfect number so we implement it:

perfect :: Int -> Bool
perfect m = sum [ y | y <- factors m, y /= m ] == m

Note We can iterate over the perfect implementation and change it using the init function (drops the last element of a list):

perfect :: Int -> Bool
perfect m = sum (init (factors m)) == m

Note

  • The implementation is litteraly the definition.
  • we have the factors function that already computes the factors of a number, so we reuse it.

We can now define the main function using the previous perfect function:

perfects :: Int -> [Int]
perfects n = [x | x <- [1..n], perfect x]

Comprehension

Show how the single comprehension [(x, y) | x ← [1, 2, 3], y ← [4, 5, 6]] with two generators can be re-expressed using two comprehensions with single generators.

Hint: Make use of the library function concat and nest one comprehension within the other.

Apparently, from the hint, is possible to nest the list comprehensions.

We will go one step at a time, first nesting:

Prelude> [[(x,y) | x <- [1,2,3]] | y <- [4,5,6]]
[[(1,4),(2,4),(3,4)],[(1,5),(2,5),(3,5)],[(1,6),(2,6),(3,6)]]

Indeed, now if we use concat, this will give us what we want:

Prelude> concat [[(x,y) | x <- [1,2,3]] | y <- [4,5,6]]
[(1,4),(2,4),(3,4),(1,5),(2,5),(3,5),(1,6),(2,6),(3,6)]

which is equal to the first result if we do not consider the list order:

Prelude> [(x, y) | x <- [1, 2, 3], y <- [4, 5, 6]]
[(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]

positions

Redefine the function positions using the function find.

Recall the find definition:

find :: Eq a => a -> [(a,a)] -> [a]
find k hs = [v | (k', v) <- hs, k == k']

We will generate the list of couples (value, position) then feed this to the find function. This will then compute all the values associated (their position) to the search value v:

positions2 :: Int -> [Int] -> [Int]
positions2 v vs = find v (zip vs [0..n])
                  where
                    n = (length vs) - 1

Here is the comparison between the first implementation and the new one:

*Ch5> positions2 1 [0,1,3,4,2,1,3]
[1,5]

scalarproduct

The scalar product of two lists of integers xs and ys of length n is given by the sum of the products of corresponding integers: sum i=0..n−1 (xsi ∗ ysi)

In a similar manner to the function chisqr, show how a list comprehension can be used to define a function scalarproduct :: [ Int ] → [ Int ] → Int that returns the scalar product of two lists.

For example: > scalarproduct [1, 2, 3] [4, 5, 6] 32

We first create the list containing the couple (x, y) then we (lazily) compute the product for each couple, then sum them all:

scalarproduct :: [Int] -> [Int] -> Int
scalarproduct xs ys = sum [x * y | (x, y) <- zip xs ys]

As an example:

*Ch5> scalarproduct [1,2,3] [4,5,6]
32

Caesar cipher

Modify the Caesar cipher program to also handle upper-case letters.

caesar program

Recall the caesar program:

low2int :: Char -> Int
low2int c = C.ord c - C.ord 'a'

int2low :: Int -> Char
int2low l = C.chr (C.ord 'a' + l)

shift :: Int -> Char -> Char
shift n c | C.isLower c = int2low ((n + low2int c) `mod` 26)
          | otherwise = c

encode :: Int -> String -> String
encode n cs = [shift n c | c <- cs]

Here is a sample or executing the first implementation:

*Ch5> encode 3 "functional programming rocks!"
"ixqfwlrqdo surjudpplqj urfnv!"

Upper

We then have to add functions that deals with upper case too.

upp2int

For this, we can add functions upp2int (dual of low2int) and int2upp (dual of int2low).

As their behaviour is similar, we can extract a char2Int function which takes the char c to transform and a reference char.

char2int :: Char -> Char -> Int
char2int c cr = C.ord c - C.ord cr

The definition of low2int and upp2int becomes:

low2int :: Char -> Int
low2int c = char2int c 'a'

upp2int :: Char -> Int
upp2int c = char2int c 'A'

int2upp

The same thing can be tell for the int 2 letter transformation:

int2char :: Int -> Char -> Char
int2char l c = C.chr (C.ord c + l)

int2low :: Int -> Char
int2low l = int2char l 'a'

int2upp :: Int -> Char
int2upp l = int2char l 'A'

shift

The function shift evolves to add a guardian entry to check on the Upper case property of a char. Again the behaviour is similar, only the reference char changes.

So we can extract a higher order function shiftchar:

shiftchar :: (Int -> Char) -> (Char -> Int) -> Int -> Char -> Char
shiftchar i2c c2i n c = i2c ((n + c2i c) `mod` 26)

The function shift becomes then:

shift :: Int -> Char -> Char
shift n c | C.isLower c = shiftchar int2low low2int n c
          | C.isUpper c = shiftchar int2upp upp2int n c
          | otherwise = c

encode

At last the function encode does not change.

Check

And the encoding final with upper case:

*Ch5> encode 3 "Functional programming ROCKS!"
"Ixqfwlrqdo surjudpplqj URFNV!"

Which is definitely the same as before but with upper letters.

final

Here is the final result:

char2int :: Char -> Char -> Int
char2int c cr = C.ord c - C.ord cr

low2int :: Char -> Int
low2int c = char2int c 'a'

upp2int :: Char -> Int
upp2int c = char2int c 'A'

int2char :: Int -> Char -> Char
int2char l c = C.chr (C.ord c + l)

int2low :: Int -> Char
int2low l = int2char l 'a'

int2upp :: Int -> Char
int2upp l = int2char l 'A'

shiftchar :: (Int -> Char) -> (Char -> Int) -> Int -> Char -> Char
shiftchar i2c c2i n c = i2c ((n + c2i c) `mod` 26)

shift :: Int -> Char -> Char
shift n c | C.isLower c = shiftchar int2low low2int n c
          | C.isUpper c = shiftchar int2upp upp2int n c
          | otherwise = c

encode :: Int -> String -> String
encode n cs = [shift n c | c <- cs]

Source

Latest posts