4.3. Filter#
Suppose we wanted to filter out only the even numbers from a list, or the odd numbers. Here are some functions to do that:
(** [even n] is whether [n] is even. *)
let even n =
n mod 2 = 0
(** [evens lst] is the sublist of [lst] containing only even numbers. *)
let rec evens = function
| [] -> []
| h :: t -> if even h then h :: evens t else evens t
let lst1 = evens [1; 2; 3; 4]
val even : int -> bool = <fun>
val evens : int list -> int list = <fun>
val lst1 : int list = [2; 4]
(** [odd n] is whether [n] is odd. *)
let odd n =
n mod 2 <> 0
(** [odds lst] is the sublist of [lst] containing only odd numbers. *)
let rec odds = function
| [] -> []
| h :: t -> if odd h then h :: odds t else odds t
let lst2 = odds [1; 2; 3; 4]
val odd : int -> bool = <fun>
val odds : int list -> int list = <fun>
val lst2 : int list = [1; 3]
Functions evens
and odds
are nearly the same code: the only essential
difference is the test they apply to the head element. So as we did with map
in the previous section, let’s factor out that test as a function. Let’s name
the function p
as short for “predicate”, which is a fancy way of saying
that it tests whether something is true or false:
let rec filter p = function
| [] -> []
| h :: t -> if p h then h :: filter p t else filter p t
val filter : ('a -> bool) -> 'a list -> 'a list = <fun>
And now we can reimplement our original two functions:
let evens = filter even
let odds = filter odd
val evens : int list -> int list = <fun>
val odds : int list -> int list = <fun>
How simple these are! How clear! (At least to the reader who is familiar with
filter
.)
4.3.1. Filter and Tail Recursion#
As we did with map
, we can create a tail-recursive version of filter
:
let rec filter_aux p acc = function
| [] -> acc
| h :: t -> if p h then filter_aux p (h :: acc) t else filter_aux p acc t
let filter p = filter_aux p []
let lst = filter even [1; 2; 3; 4]
val filter_aux : ('a -> bool) -> 'a list -> 'a list -> 'a list = <fun>
val filter : ('a -> bool) -> 'a list -> 'a list = <fun>
val lst : int list = [4; 2]
And again we discover the output is backwards. Here, the standard library makes
a different choice than it did with map
. It builds in the reversal to
List.filter
, which is implemented like this:
let rec filter_aux p acc = function
| [] -> List.rev acc (* note the built-in reversal *)
| h :: t -> if p h then filter_aux p (h :: acc) t else filter_aux p acc t
let filter p = filter_aux p []
val filter_aux : ('a -> bool) -> 'a list -> 'a list -> 'a list = <fun>
val filter : ('a -> bool) -> 'a list -> 'a list = <fun>
Why does the standard library treat map
and filter
differently on this
point? Good question. Perhaps there has simply never been a demand for a
filter
function whose time efficiency is a constant factor better. Or perhaps
it is just historical accident.
4.3.2. Filter in Other Languages#
Again, the idea of filter exists in many programming languages. Here it is in Python:
>>> print(list(filter(lambda x: x % 2 == 0, [1, 2, 3, 4])))
[2, 4]
And in Java:
jshell> Stream.of(1, 2, 3, 4).filter(x -> x % 2 == 0).collect(Collectors.toList())
$1 ==> [2, 4]