测试列表是否包含Clojure中的特定值

时间:2010-07-14 18:43:00

标签: data-structures clojure

在Clojure中测试列表是否包含给定值的最佳方法是什么?

特别是contains?的行为目前令我困惑:

(contains? '(100 101 102) 101) => false

我显然可以编写一个简单的函数来遍历列表并测试相等性,但肯定有一种标准的方法可以做到这一点吗?

18 个答案:

答案 0 :(得分:191)

啊,contains? ...据说是五大常见问题之一:Clojure。

检查集合是否包含值;它检查是否可以使用get检索项目,换句话说,检查集合是否包含密钥。这对于集合(可以被认为不区分键和值),映射(所以(contains? {:foo 1} :foo)true)和向量(但请注意(contains? [:foo :bar] 0)是{{}有意义。 1}},因为这里的键是索引,所讨论的向量“包含”索引true!)。

为了增加混乱,如果调用0没有意义,只需返回contains?;这就是false 以及 (contains? :foo 1)中发生的情况。 更新:在Clojure中≥1.5(contains? '(100 101 102) 101)投掷时一种不支持预期的“密钥成员”测试的类型的对象。

执行您要执行的操作的正确方法如下:

contains?

当搜索一堆物品中的一个时,你可以使用更大的一组;在搜索; most of the time this works (some #{101} '(100 101 102)) / false时,您可以使用nil / false? - 因为nil?会返回(#{x} x),因此x(#{nil} nil);在搜索其中一些项目中的一项时,可以nilfalse,您可以使用

nil

(请注意,这些项目可以在任何类型的集合中传递给(some (zipmap [...the items...] (repeat true)) the-collection) 。)

答案 1 :(得分:120)

这是我用于同一目的的标准工具:

(defn in? 
  "true if coll contains elm"
  [coll elm]  
  (some #(= elm %) coll))

答案 2 :(得分:14)

我知道我有点晚了,但是:

(contains? (set '(101 102 103)) 102)

最后在clojure 1.4中输出true:)

答案 3 :(得分:12)

您始终可以使用.methodName语法调用java方法。

(.contains [100 101 102] 101) => true

答案 4 :(得分:10)

(not= -1 (.indexOf '(101 102 103) 102))

有效,但下面更好:

(some #(= 102 %) '(101 102 103)) 

答案 5 :(得分:7)

对于它的价值,这是我对列表的包含函数的简单实现:

(defn list-contains? [coll value]
  (let [s (seq coll)]
    (if s
      (if (= (first s) value) true (recur (rest s) value))
      false)))

答案 6 :(得分:6)

如果您有一个向量或列表并想要检查其中是否包含,您会发现contains?不起作用。 Michał已经explained why

; does not work as you might expect
(contains? [:a :b :c] :b) ; = false

在这种情况下,您可以尝试以下四种方法:

  1. 考虑您是否真的需要矢量或列表。如果您使用了,则contains?将起作用。

    (contains? #{:a :b :c} :b) ; = true
    
  2. 使用some ,将目标包装在一个集合中,如下所示:

    (some #{:b} [:a :b :c]) ; = :b, which is truthy
    
  3. 如果您要搜索假值(falsenil),则设置为功能的快捷方式将无效。

    ; will not work
    (some #{false} [true false true]) ; = nil
    

    在这些情况下,您应该使用内置谓词函数来表示该值,false?nil?

    (some false? [true false true]) ; = true
    
  4. 如果你需要经常进行这种搜索,为它编写一个函数

    (defn seq-contains? [coll target] (some #(= target %) coll))
    (seq-contains? [true false true] false) ; = true
    
  5. 另外,请参阅Michał’s answer了解检查序列中是否包含多个目标的方法。

答案 7 :(得分:5)

这是我用于此目的的标准实用程序中的快速功能:

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

答案 8 :(得分:5)

这是经典的Lisp解决方案:

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

答案 9 :(得分:4)

我建立在“list-contains?”的j-g-faustus version之上。它现在需要任意数量的论据。

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

答案 10 :(得分:2)

就像使用一组一样简单 - 类似于地图,你可以将它放在功能位置。它评估为集合中的值(这是真实的)或nil(这是假的):

(#{100 101 102} 101) ; 101
(#{100 101 102} 99) ; nil

如果你要检查一个合理大小的矢量/列表,你将不会在运行时使用,你也可以使用set函数:

; (def nums '(100 101 102))
((set nums) 101) ; 101

答案 11 :(得分:1)

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

答案 12 :(得分:1)

推荐的方法是将some与集合一起使用 - 请参阅clojure.core/some的文档。

然后,您可以在真实的真/假谓词中使用some,例如

(defn in? [coll x] (if (some #{x} coll) true false))

答案 13 :(得分:1)

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

示例用法(?[1 2 3] 3)或(??{{1 2 3} 4 5 3)

答案 14 :(得分:0)

由于Clojure是基于Java构建的,因此您可以轻松地调用.indexOf Java函数。此函数返回集合中任何元素的索引,如果找不到此元素,则返回-1。

利用这个,我们可以简单地说:

    (not= (.indexOf [1 2 3 4] 3) -1)
    => true

答案 15 :(得分:0)

推荐的问题'解决方案是,当您寻求的价值是“零”时,它就会中断。我更喜欢这个解决方案:

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

答案 16 :(得分:0)

为此目的有方便的功能in the Tupelo library。特别是,函数contains-elem?contains-key?contains-val?非常有用。提供完整的文档in the API docs

contains-elem?是最通用的,适用于矢量或任何其他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))))

在这里,我们看到对于整数范围或混合向量,contains-elem?按预期对集合中现有和不存在的元素起作用。对于地图,我们还可以搜索任何键值对(表示为len-2向量):

 (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] ))))

搜索集合也很简单:

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

对于地图&设置,使用contains-key?查找地图条目或设置元素更简单(更有效):

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

而且,对于地图,您还可以使用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)))

如测试中所示,当搜索nil值时,这些函数中的每一个都能正常工作。

答案 17 :(得分:0)

另一个选择:

((set '(100 101 102)) 101)

使用java.util.Collection#contains():

(.contains '(100 101 102) 101)