What is the best way to test whether a list contains a given value in Clojure?
In particular, the behaviour of contains?
is currently confusing me:
If you have a vector or list and want to check whether a value is contained in it, you will find that contains? does not work. Michał has already explained why.
; does not work as you might expect
(contains? [:a :b :c] :b) ; = false
There are four things you can try in this case:
Consider whether you really need a vector or list. If you use a set instead, contains?
will work.
(contains? #{:a :b :c} :b) ; = true
Use some, wrapping the target in a set, as follows:
(some #{:b} [:a :b :c]) ; = :b, which is truthy
The set-as-function shortcut will not work if you are searching for a falsy value (false
or nil
).
; will not work
(some #{false} [true false true]) ; = nil
In these cases, you should use the built-in predicate function for that value, false? or nil?:
(some false? [true false true]) ; = true
If you will need to do this kind of search a lot, write a function for it:
(defn seq-contains? [coll target] (some #(= target %) coll))
(seq-contains? [true false true] false) ; = true
Also, see Michał’s answer for ways to check whether any of multiple targets are contained in a sequence.
It is as simple as using a set - similar to maps, you can just drop it in the function position. It evaluates to the value if in the set (which is truthy) or nil
(which is falsey):
(#{100 101 102} 101) ; 101
(#{100 101 102} 99) ; nil
If you're checking against a reasonably sized vector/list you won't have until runtime, you can also use the set
function:
; (def nums '(100 101 102))
((set nums) 101) ; 101
Here's my standard util for the same purpose:
(defn in?
"true if coll contains elm"
[coll elm]
(some #(= elm %) coll))
(defn which?
"Checks if any of elements is included in coll and says which one
was found as first. Coll can be map, list, vector and set"
[ coll & rest ]
(let [ncoll (if (map? coll) (keys coll) coll)]
(reduce
#(or %1 (first (filter (fn[a] (= a %2))
ncoll))) nil rest )))
example usage (which? [ 1 2 3 ] 3) or (which? #{ 1 2 3} 4 5 3)
The recommended way is to use some
with a set - see documentation for clojure.core/some
.
You could then use some
within a real true/false predicate, e.g.
(defn in? [coll x] (if (some #{x} coll) true false))
I know that I'm a little bit late, but what about:
(contains? (set '(101 102 103)) 102)
At last in clojure 1.4 outputs true :)