Programming in Haskell

Chapter 5 - List Comprehensions

Set comprehensions

In mathematics, the comprehension notation can be used to construct new sets from old sets.

$ \{x^2 | X \in \{1...5\}\} $

The set {1,4,9,16,25} of all numbers $x^2$ such that x is an element of the set {1...5}.

Lists Comprehensions

In Haskell, a similar comprehension notation can be used to construct new lists from old lists

$ [X^2 | x \leftarrow [1..5]] $

The list [1,4,9,16,25] of all numbers x^2 such that x is an element of the list [1..5].

Note

  • The expression x $\leftarrow$ [1..5] is called a generator, as it states how to generate values for x.
  • Comprehensions can have multiple generators, seperated by commas. For example:
In [1]:
[(x,y) | x <- [1,2,3], y <- [4,5]]
[(1,4),(1,5),(2,4),(2,5),(3,4),(3,5)]
  • Changing the order of the generators changes the order of the elements in the final list:
In [3]:
[(x,y) | y <- [4,5], x <- [1,2,3]]
[(1,4),(2,4),(3,4),(1,5),(2,5),(3,5)]
  • Multiple generators are like nested loops, with later generators as more deeply nested loops whose variables change value more frequently

  • For example:

In [4]:
[(x, y) | y <- [4,5], x <- [1,2,3]]
[(1,4),(2,4),(3,4),(1,5),(2,5),(3,5)]

x $\leftarrow$ [1,2,3] is the last generator, so the value of the x component of each pair changes most frequently

Dependent Generators

Later generators can depend on the variables that are introduced by earlier generators.

In [6]:
[(x, y) | x <- [1..3], y <- [x..3]]
[(1,1),(1,2),(1,3),(2,2),(2,3),(3,3)]

The list [(1,1),(1,2),(1,3),(2,2),(2,3),(3,3)] of all pairs of number (x,y) such that x,y are elements of the list [1..3] and $y \geq x$

Using a dependant generator we can define the library function that concatenates a list of lists:

In [7]:
concat :: [[a]] -> [a]
concat xss = [x | xs <- xss, x <- xs]
In [8]:
concat [[1,2,3], [4,5], [6]]
[1,2,3,4,5,6]

Guards

List comprehensions can use guards to restrict the values produced by earlier generators

In [9]:
[x | x <- [1..10], even x]
[2,4,6,8,10]

The list [2,4,6,8,10] of all numbers x such that x is an element of the list [1..10] and x is even

Using a guard we can define a function that maps a positive integer to its list of factors:

In [10]:
factors :: Int -> [Int]
factors n = 
    [x | x <- [1..n], n `mod` x == 0]
In [11]:
factors 15
[1,3,5,15]

A postive integer is prime if its only factors are 1 and itself. Hence, using factors we can define a function that decides if a number is prime:

In [12]:
prime :: Int -> Bool
prime n = factors n == [1,n]
In [14]:
prime 7
True
In [15]:
prime 15
False

Using a guard we can now define a function that returns the list of all primes up to a given limit:

In [18]:
primes :: Int -> [Int]
primes n = [x | x <- [1..n], prime x]

-- notice how you can get the same results
-- even if you do primes n = [x | x <- [2..n], prime x]
-- because we know 1 is not a prime and we do not have to always check for that
In [17]:
primes 40
[2,3,5,7,11,13,17,19,23,29,31,37]

The Zip function

A useful library function is zip, which maps two lists to a list of pairs of their corresponding elements.

zip :: [a] -> [b] -> [(a,b)]
In [20]:
zip ['a', 'b', 'c'] [1,2,3,4]
[('a',1),('b',2),('c',3)]

Using zip we can define a function returns the list of all pairs of adjacent elements from a list:

In [21]:
pairs :: [a] -> [(a,a)]
pairs xs = zip xs (tail xs)
In [22]:
pairs [1,2,3,4,5]
[(1,2),(2,3),(3,4),(4,5)]

Using pairs we can define a function that decides if the elements in a list are sorted:

In [23]:
sorted :: Ord a => [a] -> Bool
sorted xs = and [x <= y | (x,y) <- pairs xs]
In [24]:
sorted [1,2,3,4]
True
In [25]:
sorted [1,2,45,3,7]
False

Using zip we can define a function that returns the list of all position of a value in a list:

In [27]:
positions :: Eq a => a -> [a] -> [Int]
positions x xs =
    [i | (x', i) <- zip xs [0..], x == x']
In [28]:
positions 0 [1,0,0,1,0,1,1,0]
[1,2,4,7]

String Comprehensions

A string is a sequence of characters enclosed in double quotes. Internally, however, strings are represented as lists of characters.

"abc" :: String

Means ['a','b','c'] :: Char

Because strings are just special kinds of lists, any polymorphic function that operates on lists can also be applied to strings. For example:

In [29]:
length "abcde"
5
In [30]:
take 3 "abcde"
"abc"
In [31]:
zip "abc" [1,2,3,4]
[('a',1),('b',2),('c',3)]

Similarly, list comprehensions can also be used to define functions on strings, such as counting how many times a character occurs in a string:

In [33]:
count :: Char -> String -> Int
count x xs = length [x' | x' <- xs, x' == x]
In [34]:
count 's' "Mississippi"
4

Exercises

  1. A triple (x,y,z) of positive integers is called pythagorean if $x^2 + y^2 = z^2$. Using a list comprehension, define a function
pyths :: Int -> [(Int, Int, Int)]

That maps an integer n to all such triples with components in [1..n].

In [43]:
pyths :: Int -> [(Int, Int, Int)]
pyths x = [(x',y,z) | x' <- [1..x], y <- [1..x], z <- [1..x], x'^2 + y^2 == z^2]
In [44]:
pyths 5
[(3,4,5),(4,3,5)]
  1. A positive integer is perfect if it equals the sum of all of its factors, excluding the number itself. Using a list comprehension, define a function
perfects :: Int -> [Int]
In [53]:
perfects :: Int -> [Int]
perfects x = [x' | x' <- [1..x], sum (factors x') - x' == x']
In [54]:
perfects 500
[6,28,496]
  1. The scalar product of two lists of integers $xs$ and $ys$ of length n is given by the sum of the products of the corresponding integers:

$ \sum ^{n-1}_{i=0} (xs_i * ys_i) $

Using a list comprehension, define a function that returns the scalar product of two lists.

In [3]:
scalarproduct :: [Int] -> [Int] -> Int
scalarproduct xs ys = sum [fst xys * snd xys | xys <- zip xs ys]
Use uncurry
Found:
fst xys * snd xys
Why Not:
uncurry (*) xys
In [4]:
scalarproduct [1,2,3] [1,2,3]
14
In [ ]: