filter and find
Sometimes, we may want to extract all those elements from a list that meet some
condition. filter
is the tool for the job. For example,
>>> filter even [1,2,3,4,5]
[2,4]
Here's how filter
is implemented:
filter :: (a -> Bool) -> [a] -> [a]
filter pred = go
where
go (x:xs) | pred x = x : go xs
| otherwise = go xs
go _ = []
If we want to find just a single element in the list that meets the given
condition, we can use find
:
>>> import Data.List
>>> find even [1,2,3,4,5]
Just 2
>>> find even [1,3,5]
Nothing
Find can be implemented directly,
find :: (a -> Bool) -> [a] -> Maybe a
find pred = go
where
go (x:xs) | pred x = Just x
| otherwise = go xs
go _ = Nothing
However, we can also implement it with the help of the following little function:
listToMaybe :: [a] -> Maybe a
listToMaybe [] = Nothing
listToMaybe (x:_) = Just x
This is actually nothing but our safe head function discussed earlier. It
returns Just
the head of the list if the list is non-empty, and Nothing
otherwise. To use this function, you need to import the Data.Maybe
module.
With the help of listToMaybe
, find
becomes really easy to implement:
find pred = listToMaybe . filter pred
The logic is that filter
produces the list of all elements that meet the given
criterion. If this list is empty, listToMaybe
turns this into Nothing
. If
the list is non-empty, listToMaybe
turns this into Just
the head of the
list, so we obtain exactly the correct behaviour of find
.
You may wonder whether this implementation of find
via filter
and
listToMaybe
is less efficient than the direct implementation of find
given
above. In most languages, this would be the case because the direct
implementation of find
stops as soon as it finds the first element that meets
the given condition, whereas filter
inspects all list elements to decide
whether they meet the given condition, and then we use listToMaybe
to extract
the first element from the list of elements that do. Given Haskell's lazy
evaluation, filter pred
does not inspect any elements in its input list unless
we inspect elements in the result of filter pred
. When we do, filter pred
inspects exactly as many elements in the input list as necessary to produce the
number of elements in the output list we inspect. Since find pred
is
interested only in the first element that matches the predicate pred
,
filter pred
inspects exactly as many elements in the input list as necessary
to find the first element that matches pred
or to verify that none of the
elements in the input list satisfies pred
. That's exactly the same behaviour
as exhibited by the direct implementation of find
, that is, lazy evaluation
makes the implementation of find
via filter
exactly as efficient as the
direct implementation of find
.
Exercise
Instead of finding the first or all list elements that satisfy a certain
condition, we sometimes want to split a list into two sublists based on some
condition. We have already seen partition
, which produces two lists
containing the elements that do and do not satisfy the condition,
respectively:
>>> import Data.List
>>> partition even [1,2,3,4,5]
([2,4],[1,3,5])
There are two other functions that have the same type as partition
but
behave differently:
span :: (a -> Bool) -> [a] -> ([a], [a])
break :: (a -> Bool) -> [a] -> ([a], [a])
span pred xs
splits xs
into two lists ys
and zs
, where ys
is the
longest prefix of xs
whose elements satisfy pred
, and zs
is the
remainder of xs
. In other words, span pred xs
takes elements from xs
and adds them to ys
as long as these elements satisfy pred
. As soon as
it sees an element that does not satisfy pred
, it stops and defines zs
to be the list of remaining elements in xs
:
>>> span even [2,8,4,1,10,12,3,7]
([2,8,4],[1,10,12,3,7])
break pred xs
does the opposite. It adds elements to ys
as long as they
don't satisfy pred
. As soon as it finds an element that does satisfy
pred
, it stops and defines zs
to be the list of remaining elements in
xs
:
>>> break odd [2,8,4,1,10,12,3,7]
([2,8,4],[1,10,12,3,7])
There also exist two functions takeWhile
and dropWhile
that return only
one of the two lists returned by span
. In other words, we have
span pred xs = (takeWhile pred xs, dropWhile pred xs)
only the actual implementation of span
is more efficient.
>>> takeWhile even [2,8,4,1,10,12,3,7]
[2,8,4]
>>> dropWhile even [2,8,4,1,10,12,3,7]
[1,10,12,3,7]
Implement span
and provide implementations of break
, takeWhile
, and
dropWhile
that use span
as a helper.
Solution
span :: (a -> Bool) -> [a] -> ([a], [a])
span pred = go
where
go [] = ([], [])
go xs@(x:xs')
| pred xs = (x : ls, rs)
| otherwise = ([], xs)
where
(ls, rs) = go xs'
break :: (a -> Bool) -> [a] -> ([a], [a])
break pred = span (not . pred)
takeWhile, dropWhile :: (a -> Bool) -> [a] -> [a]
takeWhile pred = fst . span pred
dropWhile pred = snd . span pred