Clojure: contains? and some
Checking for containment, whether a collection contains an item, is a common task in software development. This post covers the ways you can check for containment in Clojure.
Contains?
The contains?
function springs to mind, lets try it out in the repl.
(contains? {:a 1 :b 2 :c 3} :a)
=> true
(contains? {:a 1 :b 2 :c 3} :d)
=> false
So far so good, contains?
works as expected with maps.
(contains? #{:a :b :c} :a)
=> true
(contains? #{:a :b :c} :d)
=> false
Looks like it also works with sets.
(contains? [1 :b 3 :a] :a)
=> false
(contains? [2 3 4] 1)
=> true
(contains? [1 3 4] 4)
=> false
What's going on here? We are not getting "expected" behaviour with vectors. Let's look at the docs.
(doc contains?)
=>
-------------------------
clojure.core/contains?
([coll key])
Returns true if key is present in the given collection, otherwise
returns false. Note that for numerically indexed collections like
vectors and Java arrays, this tests if the numeric key is within the
range of indexes. 'contains?' operates constant or logarithmic time;
it will not perform a linear search for a value. See also 'some'.
So for vectors, contains?
is only useful for checking if an array contains an index, not a value.
Some
The documentation for contains?
mentions the some
function. Let's investigate the docs for some
.
(doc some)
=>
-------------------------
clojure.core/some
([pred coll])
Returns the first logical true value of (pred x) for any x in coll,
else nil. One common idiom is to use a set as pred, for example
this will return :fred if :fred is in the sequence, otherwise nil:
(some #{:fred} coll)
So we can use some
to check if a collection contains an item that satisfies the supplied predicate.
(some even? [1 2 3])
=> true
(some even? [1 3 5])
=> nil
(some identity [nil false nil 4 nil])
=> 4
You can also use some
to check for containment by passing in a set containing the value you want to check for, eg: #{:a}
. This works because sets are functions in Clojure that return the value you pass into them if it is contained in the set and nil otherwise. Finally some
can also be used for checking if a collection contains at least one of the values in the set, returning the first true value it encounters.
(some #{:a} [1 :b :a 2])
=> :a
(some #{:d} [1 :b :a 2])
=> nil
(some #{:a :b} [1 :b :a 2])
=> :b
One thing to bear in mind when using some
with sets is that it won't work with nil
or false
values.
(some #{false nil} [false true false nil])
=> nil
Hopefully this covers most of your day to day containment needs.