To run the tests of this lesson, run from your terminal
bin/kaocha --focus academy.predicates-test
So how do you write a funtion that takes a function as a parameter? Well, for
starters, let’s define a function called (apply-1 f x)
that should return
(f x)
:
(defn apply-1 [f x]
(f x))
When you take a function as a parameter, you treat it like any other parameter. In clojure functions are just normal values and you can pass them around however you like. Calling a function parameter works like calling any other function.
(apply-1 str 13) ;=> (str 13) ;=> "13"
Write the function (sum-f f g x)
that returns (+ (f x) (g x))
.
(sum-f inc dec 4) ;=> 8
(sum-f inc identity 5) ;=> 11
(sum-f identity - 10) ;=> 0
A function that returns a boolean value (false
or true
) is called a
predicate. Usually their name ends in a question mark ?
. We’ve already
defined a bunch of them and used them with filter
.
Let’s write a function that returns a predicate. Suppose that I want to get all numbers greater than some limit from a sequence. I need a predicate for this grater than testing. Here’s a function that returns such predicates.
(defn greater-than [n]
(fn [k] (> k n)))
We’ve already formed helper functions with fn
. Since these functions are
just values, we can just return them.
Let’s try it out:
(filter (greater-than 5) [4 6 5 0 9 2 12]) ;=> (6 9 12)
Write the functions (less-than n)
and (equal-to n)
that work like
greater-than
. Use ==
as comparison in equal-to
(filter (less-than 3) [1 2 3 4 5]) ;=> (1 2)
(filter (less-than 4) [-2 12 3 4 0]) ;=> (-2 3 0)
(filter (equal-to 2) [2 1 3 2.0]) ;=> (2 2.0)
(filter (equal-to 2) [3 4 5 6]) ;=> ()
When you have a bunch of maps, you often want to filter those that have a
certain :keyword
as a key. Most of the time, you can just use the :keyword
itself as a predicate.
Here are some graphic novels, some of them belong to a series (and thus have the :series
keyword) and some are stand
alones.
(def graphic-novels
[{:name "Yotsuba 1" :series "Yotsuba"}
{:name "Yotsuba 5" :series "Yotsuba"}
{:name "Persepolis"}
{:name "That Yellow Bastard" :series "Sin City"}
{:name "The Hard Goodbye" :series "Sin City"}
{:name "Maus"}
{:name "Solanin"}
{:name "Monster 3" :series "Monster"}])
Now it’s easy to filter those that belong to a series:
(filter :series graphic-novels)
;=> ({:name "Yotsuba 1", :series "Yotsuba"}
; {:name "Yotsuba 5", :series "Yotsuba"}
; {:name "That Yellow Bastard", :series "Sin City"}
; {:name "The Hard Goodbye", :series "Sin City"}
; {:name "Monster 3", :series "Monster"})
Trouble arises when the value for :keyword
can be nil
or false
. No
worries though, we can write a function that turns any key into a predicate.
(defn key->predicate [a-key]
(fn [a-map] (contains? a-map a-key)))
And these predicates can be used in the same way.
(filter (key->predicate "Name") [{"Name" "Joe"} {"Blargh" 3}])
;=> ({"Name" "Joe"})
A similar thing happens when you want to use a set as a predicate. Set’s act
as functions and they work like this. (a-set x)
evaluates to
x
if x
is in a-set
nil
otherwiseNow you can use a set as a predicate as long as it doesn’t contain false
or
nil
. You can, for example, use this with filter
to get all element’s in a
sequence that are also elements of a set:
(filter #{1 2 3} [0 2 4 6]) ;=> (2)
Trouble arises, as mentioned, if the set happens to have falsey values in it.
(filter #{1 2 3 nil} [0 2 4 6 nil]) ;=> (2)
Let’s write a function that turns sets into predicates that works correctly even in this case.
Write the function (set->predicate a-set)
that takes a-set
as a parameter
and returns a predicate that takes x
as a parameter and
true
if x
is in a-set
false
(filter (set->predicate #{1 2 3}) [0 2 4 6]) ;=> (2)
(filter (set->predicate #{1 2 3 nil}) [2 nil 4 nil 6]) ;=> (2 nil nil)
Sometimes you have a predicate that almost does what you want. For an example,
suppose I want to filter all non-negative values from a sequence. There’s
neg?
and pos?
, but neither allow 0
. I could do something like this:
(defn non-negatives [a-seq]
(let [non-negative? (fn [n] (not (neg? n)))]
(filter non-negative? a-seq)))
(non-negatives [1 -2 9 4 0 -100 2 0 2]) ;=> (1 0 3 0 7)
Wanting to get the opposite result of a predicate is actually common enough
that there’s a function for this. It is called complement
. (complement
pred)
takes a predicate and returns a new predicate that returns true
when pred
returns a falsey value and false
when pred
returns a truthy
value.
((complement neg?) -5)
;=> (not (neq? -5))
;=> (not true)
;=> false
((complement neg?) 0) ;=> true
((complement neg?) 12) ;=> true
Now we can write non-negatives
using complement
:
(defn non-negatives [a-seq]
(filter (complement neg?) a-seq))
Okay, so complement
both takes a function as a parameter and returns a new
function. Let’s see the definition of complement
:
(defn complement [predicate]
(fn [x] (not (predicate x))))
Sometimes you have multiple predicates and you want to know whethet a value or some values pass all of them. Let’s create a helper function to do just that.
Write the function (pred-and pred1 pred2)
that returns a new predicate that:
true
if both pred1
and pred2
return true
false
Suppose I wanted all even positive numbers from a sequence. With pred-and
,
this should be easy.
(filter (pred-and pos? even?) [1 2 -4 0 6 7 -3]) ;=> [2 6]
Here are some other test cases:
(filter (pred-and pos? odd?) [1 2 -4 0 6 7 -3]) ;=> [1 7]
(filter (pred-and (complement nil?) empty?) [[] '() nil {} #{}])
;=> [[] '() {} #{}]
Write the function (pred-or pred1 pred2)
that takes two predicates and
returns a new predicate that:
true
if pred1
or pred2
returns truefalse
(filter (pred-or pos? odd?) [1 2 -4 0 6 7 -3]) ;=> [1 2 6 7 -3]
(filter (pred-or pos? even?) [1 2 -4 0 6 7 -3]) ;=> [1 2 -4 0 6 7]
Sometimes you want to know if every element in a sequence satisfies a
predicate. To do that, there’s (every? predicate a-seq)
. It returns true
if predicate
returns a truthy value for every element in a-seq
. Otherwise
it returns false
.
(every? pos? [1 2 3]) ;=> true
(every? pos? [0 1 2 3]) ;=> false -- (pos? 0) ;=> false
It also always returns true
with an empty collection.
(every? pos? []) ;=> true
(every? pos? nil) ;=> true
Write the function (blank? string)
that takes a string as a parameter and
returns true
if string
is empty, nil, or contains only whitespace.
Remember that strings can be used as a sequence of characters with
sequence functions like every?
. A function whitespace?
that tells
if a character is whitespace is defined for you in the source file.
(blank? " \t\n\t ") ;=> true
(blank? " \t a") ;=> false
(blank? "") ;=> true
Earlier we had some books, let’s add some data to them. Let’s keep track of some awards.
(def china {:name "China Miéville", :birth-year 1972})
(def octavia {:name "Octavia E. Butler"
:birth-year 1947
:death-year 2006})
(def kay {:name "Guy Gavriel Kay" :birth-year 1954})
(def dick {:name "Philip K. Dick", :birth-year 1928, :death-year 1982})
(def zelazny {:name "Roger Zelazny", :birth-year 1937, :death-year 1995})
(def authors #{china, octavia, kay, dick, zelazny})
(def cities {:title "The City and the City" :authors #{china}
:awards #{:locus, :world-fantasy, :hugo}})
(def wild-seed {:title "Wild Seed", :authors #{octavia}})
(def lord-of-light {:title "Lord of Light", :authors #{zelazny}
:awards #{:hugo}})
(def deus-irae {:title "Deus Irae", :authors #{dick, zelazny}})
(def ysabel {:title "Ysabel", :authors #{kay}, :awards #{:world-fantasy}})
(def scanner-darkly {:title "A Scanner Darkly" :authors #{dick}})
(def books #{cities, wild-seed, lord-of-light,
deus-irae, ysabel, scanner-darkly}])
Write the function (has-award? book award)
that returns true
if book
has
won award
.
(has-award? ysabel :world-fantasy) ;=> true
(has-award? scanner-darkly :hugo) ;=> false
Write the function (HAS-ALL-THE-AWARDS? book awards)
that returns true
if
book
has won every award in awards
.
(HAS-ALL-THE-AWARDS? cities #{:locus})
;=> true
(HAS-ALL-THE-AWARDS? cities #{:locus :world-fantasy :hugo})
;=> true
(HAS-ALL-THE-AWARDS? cities #{:locus :world-fantasy :hugo :pulitzer})
;=> false
(HAS-ALL-THE-AWARDS? lord-of-light #{:locus :world-fantasy :hugo})
;=> false
(HAS-ALL-THE-AWARDS? lord-of-light #{:hugo})
;=> true
(HAS-ALL-THE-AWARDS? scanner-darkly #{})
;=> true
Finally, when you wan’t to know if at least one element of a sequence passes a
predicate, there is (some pred a-seq)
which returns a truthy value if pred
returns a truthy value for some element in a-seq
and otherwise it returns
nil
.
(some whitespace? "Kekkonen") ;=> nil
(some whitespace? "Kekkonen Kekkonen") ;=> True
(some even? [1 2 3]) ;=> true
(some even? [1 3]) ;=> false
Why some
and not some?
? Well, some
is not actually a predicate.
It returns the first truthy value returned by pred
, and functions
ending in ?
should return strictly true
or false
. This is often
useful, but sometimes you need to be careful with it.
(some first [[] [1 2] []]) ;=> 1
(some first [[] [false true] []] ;=> nil
(some nil? [1 nil 2]) ;=> true
Write you own implementation for some
called my-some
.
Hint: You might find map
, filter
and first
useful (you won’t necessarily
need them all).
(my-some even? [1 3 5 7]) ;=> falsey
(my-some even? [1 3 5 7 8]) ;=> true
(my-some neg? [1 3 5 0 7 8]) ;=> falsey
(my-some neg? [1 3 5 0 7 -1 8]) ;=> true
(my-some neg? []) ;=> falsey
(my-some first [[false] [1]]) ;=> 1
(my-some first [[false] []]) ;=> falsey
(my-some nil? [1 2]) ;=> falsey
(my-some nil? [1 nil 2]) ;=> true
Write your own implementation for every?
called my-every?
.
Hint: the previous hint applies. empty?
and complement
might come in handy
as well.
(my-every? pos? [1 2 3 4]) ;=> true
(my-every? pos? [1 2 3 4 0]) ;=> false
(my-every? even? [2 4 6]) ;=> true
(my-every? even? []) ;=> true
Write the function (prime? n)
that returns true
if n
is a
prime number and otherwise false
.
The function (range k n)
returns the sequence
(k (+ k 1) (+ k 2) ... (- n 1))
Here’s a concrete example:
(range 2 10) ;=> (2 3 4 5 6 7 8 9)
A positive integer p
is a prime number if the only positive numbers dividing
p
are p
and 1
.
Your solution should be of the form
(defn prime? [n]
(let [pred ...]
(not (some pred (range 2 n)))))
Here are some tests:
(prime? 4) ;=> false
(prime? 7) ;=> true
(prime? 10) ;=> false
(filter prime? (range 2 50)) ;=> (2 3 5 7 11 13 17 19 23 29 31 37 41 43 47)