views:

98

answers:

4

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?

+10  A: 

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 contains?, it simply return false; this is what happens in (contains? :foo 1) and also (contains? '(100 101 102) 101).

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.)

Michał Marczyk
Thanks Michal - you are a font of Clojure wisdom as always! Looks like I'm going to write my own function in this case... it slightly surprises me that there isn't one already in the core language.
mikera
As Michal said - there is already a function in core which does what you desire: some.
kotarak
+3  A: 

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))))
Greg Harman
Thanks Greg! I just wrote something vaugely similar, posted it above....
mikera
Yeah, yours has the advantage that it will stop as soon as it finds a match rather than continuing to map the entire sequence.
Greg Harman
+3  A: 

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)))
mikera
+3  A: 

Here's my standard util for the same purpose:

(defn in? 
  "true if seq contains elm"
  [seq elm]  
  (some #(= elm %) seq))
j-g-faustus
Thanks for adding to the collection! Seems like everyone has found the need for one of these :-)
mikera
Seems like a popular pastime, indeed. Perhaps we'll end up with a collection of all possible ways to implement it? :)
j-g-faustus