Manipulating Collections - filter and partial¶
The filter
function processes a collection and yields a sequence of the items that fit certain criteria.
Here is the general form of the filter
function that we will use:
(filter predicate collection)
where
predicate
is a function that has one parameter: the collection item under considerationcollection
is the collection to be filtered.
filter
applies the predicate
function to each item in the collection. If the function returns true
, the item goes into the output sequence, otherwise it doesn’t.
As an example, here is filter
used to extract only numbers that are multiples of four:
Note
By convention, boolean functions (functions that return true
or false
) have
names ending with a question mark. Some of the built-in predicates in ClojureScript
are even?
, odd?
, pos?
(positive), neg?
(negative), zero?
, and empty?
(tests
if a collection is empty or not).
Functions that Create Functions: partial
¶
Now, let’s say you wanted to write a filter for a set of scores that keeps only the scores that are above average. You have to go through the scores twice: once to find the average, and then to filter the data that you want. (In computer science, this is called a “two-pass algorithm.”) You would like to write something like this:
(def scores [81 72 95 84 91 77])
(defn average [coll]
(let [n (count coll)
total (reduce + coll)]
(if (> n 0) (/ total n) 0)))
; this filter function will not work
(defn above-average? [avg value]
(> value avg))
(let [avg (average scores)]
(filter above-average? scores))
There’s only one problem with this: the predicate function to filter
takes only a single argument, and you need two pieces of information: the current score and the average. You can’t write the above-average?
function like this:
(defn above-average? [value]
(> value avg))
because you don’t know the average in advance. You could use def
to bind avg
to some value and then change it later with set!
, but that goes counter to the principles of functional programming—you don’t want to re-bind a symbol to a new value if you don’t have to, and in this case, you don’t have to.
Instead, you can use partial
. Before showing you how it is written, here’s the idea behind it. When you log in to an e-commerce site you’ve used before to purchase a new item, the checkout form shows up with much of the information already filled in (name, shipping/billing address). All you need to do is complete one or two items. In essence, the form is partially filled in.
So here’s the trick: we’re going to give partial
the two-parameter function and the first argument. The result will be a partially-called function that is waiting to be called with the second argument, much as the web site sends you a partially-completed form that is waiting to be completed with extra information.
Before doing the whole program, let’s see how you could write a partial
function for filtering averages above 50. I’ve put the symbols in the expression in above-average
in the same order as the parameters.
A picture of what’s happening here might be useful:
Essentially, partial
has constructed a one-parameter function with the 50 “baked in” as the first argument. Thus, here is the solution for filtering only those scores greater than the average:
The key here is above-calculated-average? (partial above-average? avg)
. As soon as you do know
the average of the scores, you use partial
to provide a new function of one argument that returns
true
for values above the avg
, which is exactly what filter
requires.
Exercise Remember a few pages back where you used map
to calculate a 10% discount on a vector of
prices? This time, you are going to write a program that uses map
to calculate a discount that’s
passed as an argument. Complete this code; you will use partial
to get the job done.
The multi-line quoted string between discount
and its arguments is a conventional way to
comment a function in ClojureScript.