Reputation: 106401
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:
(contains? '(100 101 102) 101) => false
I could obviously write a simple function to traverse the list and test for equality, but there must surely be a standard way to do this?
Upvotes: 197
Views: 104374
Reputation: 4508
Found this late. But this is what im doing
(some (partial = 102) '(101 102 103))
Upvotes: 0
Reputation: 183
Another option:
((set '(100 101 102)) 101)
Use java.util.Collection#contains():
(.contains '(100 101 102) 101)
Upvotes: 1
Reputation: 1704
(not= -1 (.indexOf '(101 102 103) 102))
Works, but below is better:
(some #(= 102 %) '(101 102 103))
Upvotes: 16
Reputation: 53
Since Clojure is built on Java, you can just as easily call the .indexOf
Java function. This function returns the index of any element in a collection, and if it can't find this element, returns -1.
Making use of this we could simply say:
(not= (.indexOf [1 2 3 4] 3) -1)
=> true
Upvotes: 1
Reputation: 672
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 :)
Upvotes: 18
Reputation: 29984
There are convenient functions for this purpose in the Tupelo library. In particular, the functions contains-elem?
, contains-key?
, and contains-val?
are very useful. Full documentation is present in the API docs.
contains-elem?
is the most generic and is intended for vectors or any other clojure seq
:
(testing "vecs"
(let [coll (range 3)]
(isnt (contains-elem? coll -1))
(is (contains-elem? coll 0))
(is (contains-elem? coll 1))
(is (contains-elem? coll 2))
(isnt (contains-elem? coll 3))
(isnt (contains-elem? coll nil)))
(let [coll [ 1 :two "three" \4]]
(isnt (contains-elem? coll :no-way))
(isnt (contains-elem? coll nil))
(is (contains-elem? coll 1))
(is (contains-elem? coll :two))
(is (contains-elem? coll "three"))
(is (contains-elem? coll \4)))
(let [coll [:yes nil 3]]
(isnt (contains-elem? coll :no-way))
(is (contains-elem? coll :yes))
(is (contains-elem? coll nil))))
Here we see that for an integer range or a mixed vector, contains-elem?
works as expected for both existing and non-existant elements in the collection. For maps, we can also search for any key-value pair (expressed as a len-2 vector):
(testing "maps"
(let [coll {1 :two "three" \4}]
(isnt (contains-elem? coll nil ))
(isnt (contains-elem? coll [1 :no-way] ))
(is (contains-elem? coll [1 :two]))
(is (contains-elem? coll ["three" \4])))
(let [coll {1 nil "three" \4}]
(isnt (contains-elem? coll [nil 1] ))
(is (contains-elem? coll [1 nil] )))
(let [coll {nil 2 "three" \4}]
(isnt (contains-elem? coll [1 nil] ))
(is (contains-elem? coll [nil 2] ))))
It is also straightforward to search a set:
(testing "sets"
(let [coll #{1 :two "three" \4}]
(isnt (contains-elem? coll :no-way))
(is (contains-elem? coll 1))
(is (contains-elem? coll :two))
(is (contains-elem? coll "three"))
(is (contains-elem? coll \4)))
(let [coll #{:yes nil}]
(isnt (contains-elem? coll :no-way))
(is (contains-elem? coll :yes))
(is (contains-elem? coll nil)))))
For maps & sets, it is simpler (& more efficient) to use contains-key?
to find a map entry or a set element:
(deftest t-contains-key?
(is (contains-key? {:a 1 :b 2} :a))
(is (contains-key? {:a 1 :b 2} :b))
(isnt (contains-key? {:a 1 :b 2} :x))
(isnt (contains-key? {:a 1 :b 2} :c))
(isnt (contains-key? {:a 1 :b 2} 1))
(isnt (contains-key? {:a 1 :b 2} 2))
(is (contains-key? {:a 1 nil 2} nil))
(isnt (contains-key? {:a 1 :b nil} nil))
(isnt (contains-key? {:a 1 :b 2} nil))
(is (contains-key? #{:a 1 :b 2} :a))
(is (contains-key? #{:a 1 :b 2} :b))
(is (contains-key? #{:a 1 :b 2} 1))
(is (contains-key? #{:a 1 :b 2} 2))
(isnt (contains-key? #{:a 1 :b 2} :x))
(isnt (contains-key? #{:a 1 :b 2} :c))
(is (contains-key? #{:a 5 nil "hello"} nil))
(isnt (contains-key? #{:a 5 :doh! "hello"} nil))
(throws? (contains-key? [:a 1 :b 2] :a))
(throws? (contains-key? [:a 1 :b 2] 1)))
And, for maps, you can also search for values with contains-val?
:
(deftest t-contains-val?
(is (contains-val? {:a 1 :b 2} 1))
(is (contains-val? {:a 1 :b 2} 2))
(isnt (contains-val? {:a 1 :b 2} 0))
(isnt (contains-val? {:a 1 :b 2} 3))
(isnt (contains-val? {:a 1 :b 2} :a))
(isnt (contains-val? {:a 1 :b 2} :b))
(is (contains-val? {:a 1 :b nil} nil))
(isnt (contains-val? {:a 1 nil 2} nil))
(isnt (contains-val? {:a 1 :b 2} nil))
(throws? (contains-val? [:a 1 :b 2] 1))
(throws? (contains-val? #{:a 1 :b 2} 1)))
As seen in the test, each of these functions works correctly when for searching for nil
values.
Upvotes: 0
Reputation: 21
(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)
Upvotes: 1
Reputation: 396
The problem with the 'recommended' solution is it is breaks when the value you are seeking is 'nil'. I prefer this solution:
(defn member?
"I'm still amazed that Clojure does not provide a simple member function.
Returns true if `item` is a member of `series`, else nil."
[item series]
(and (some #(= item %) series) true))
Upvotes: 0
Reputation: 172
Here's the classic Lisp solution:
(defn member? [list elt]
"True if list contains at least one instance of elt"
(cond
(empty? list) false
(= (first list) elt) true
true (recur (rest list) elt)))
Upvotes: 5
Reputation: 9019
Here's my standard util for the same purpose:
(defn in?
"true if coll contains elm"
[coll elm]
(some #(= elm %) coll))
Upvotes: 163
Reputation: 84379
Ah, contains?
... supposedly one of the top five FAQs re: Clojure.
It does not check whether a collection contains a value; it checks whether an item could be retrieved with get
or, in other words, whether a collection contains a key. This makes sense for sets (which can be thought of as making no distinction between keys and values), maps (so (contains? {:foo 1} :foo)
is true
) and vectors (but note that (contains? [:foo :bar] 0)
is true
, because the keys here are indices and the vector in question does "contain" the index 0
!).
To add to the confusion, in cases where it doesn't make sense to call Update: In Clojure ≥ 1.5 contains?
, it simply return false
; this is what happens in (contains? :foo 1)
and also (contains? '(100 101 102) 101)
.contains?
throws when handed an object of a type that doesn't support the intended "key membership" test.
The correct way to do what you're trying to do is as follows:
; most of the time this works
(some #{101} '(100 101 102))
When searching for one of a bunch of items, you can use a larger set; when searching for false
/ nil
, you can use false?
/ nil?
-- because (#{x} x)
returns x
, thus (#{nil} nil)
is nil
; when searching for one of multiple items some of which may be false
or nil
, you can use
(some (zipmap [...the items...] (repeat true)) the-collection)
(Note that the items can be passed to zipmap
in any type of collection.)
Upvotes: 239
Reputation: 30428
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.
Upvotes: 7
Reputation: 1372
You can always call java methods with .methodName syntax.
(.contains [100 101 102] 101) => true
Upvotes: 34
Reputation: 20337
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
Upvotes: 3
Reputation: 646
(defn in?
[needle coll]
(when (seq coll)
(or (= needle (first coll))
(recur needle (next coll)))))
(defn first-index
[needle coll]
(loop [index 0
needle needle
coll coll]
(when (seq coll)
(if (= needle (first coll))
index
(recur (inc index) needle (next coll))))))
Upvotes: 1
Reputation: 878
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))
Upvotes: 1
Reputation: 6931
I've built upon j-g-faustus version of "list-contains?". It now takes any number of arguments.
(defn list-contains?
([collection value]
(let [sequence (seq collection)]
(if sequence (some #(= value %) sequence))))
([collection value & next]
(if (list-contains? collection value) (apply list-contains? collection next))))
Upvotes: 4
Reputation: 106401
For what it is worth, this is my simple implementation of a contains function for lists:
(defn list-contains? [coll value]
(let [s (seq coll)]
(if s
(if (= (first s) value) true (recur (rest s) value))
false)))
Upvotes: 7
Reputation: 7111
Here's a quick function out of my standard utilities that I use for this purpose:
(defn seq-contains?
"Determine whether a sequence contains a given item"
[sequence item]
(if (empty? sequence)
false
(reduce #(or %1 %2) (map #(= %1 item) sequence))))
Upvotes: 7