Test whether a list contains a specific value in Clojure

后端 未结 18 2199
自闭症患者
自闭症患者 2020-11-30 17:17

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:

相关标签:
18条回答
  • 2020-11-30 17:50

    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:

    1. Consider whether you really need a vector or list. If you use a set instead, contains? will work.

      (contains? #{:a :b :c} :b) ; = true
      
    2. Use some, wrapping the target in a set, as follows:

      (some #{:b} [:a :b :c]) ; = :b, which is truthy
      
    3. 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
      
    4. 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.

    0 讨论(0)
  • 2020-11-30 17:51

    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
    
    0 讨论(0)
  • 2020-11-30 17:54

    Here's my standard util for the same purpose:

    (defn in? 
      "true if coll contains elm"
      [coll elm]  
      (some #(= elm %) coll))
    
    0 讨论(0)
  • 2020-11-30 17:55
    (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)

    0 讨论(0)
  • 2020-11-30 17:57

    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))
    
    0 讨论(0)
  • 2020-11-30 17:58

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

    0 讨论(0)
提交回复
热议问题