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

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

特别是,contains?的行为目前让我感到困惑:

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

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

89321 次浏览

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

它检查集合是否包含值; 它检查是否可以用 get检索项,或者换句话说,检查集合是否包含键。这对于集合(可以认为是不区分键和值)、映射(所以 (contains? {:foo 1} :foo)true)和向量(但是请注意,(contains? [:foo :bar] 0)true,因为这里的键是索引,而有问题的向量确实“包含”了索引 0!)是有意义的.

为了增加混淆,在调用 contains?没有意义的情况下,它只返回 false; 这就是在 (contains? :foo 1) 还有 (contains? '(100 101 102) 101)中发生的情况。在 Clojure 中,当给出一个不支持预期的“键成员关系”测试的类型的对象时,更新:抛出≥1.5个 contains?

正确的方法是:

; most of the time this works
(some #{101} '(100 101 102))

当搜索一组项目中的一个时,你可以使用一个较大的集合; 当搜索 false/nil时,你可以使用 false?/nil?——因为 (#{x} x)返回 x,因此 (#{nil} nil)nil; 当搜索多个项目中的一个,其中一些可能是 falsenil,你可以使用

(some (zipmap [...the items...] (repeat true)) the-collection)

(注意,这些项可以在任何类型的集合中传递给 zipmap。)

下面是我用于此目的的标准实用程序中的一个快速函数:

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

值得一提的是,这是我对列表的一个包含函数的简单实现:

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

下面是我的标准工具,用于同样的目的:

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

我已经建立在 j-g-faustus 版本的“ list-include?”的基础上,它现在接受任意数量的参数。

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

我知道我有点迟到了,但是:

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

最后在 clojure 1.4中输出 true:)

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

有用,但以下内容更好:

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

推荐的方法是使用带有 clojure.core/some的 set-see 文档的 some

然后可以在真正的 true/false 谓词中使用 some,例如。

(defn in? [coll x] (if (some #{x} coll) true false))
(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))))))

如果您有一个向量或列表,并希望检查其中是否包含 价值,则会发现 contains?不起作用。 Micha 已经拿到 解释了原因了。

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

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

  1. 考虑你是否真的需要一个矢量或列表。如果你的 使用一套代替contains?将工作。

    (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
    

    在这些情况下,对于该值,应该使用 使用内置谓词函数false?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.

下面是经典的 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)))

它就像使用一个集合一样简单——类似于映射,你只需要把它放在函数的位置。如果在集合中(为 true)或 nil(为 false) ,则计算结果为值:

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

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

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

您总是可以使用.methodName 语法调用 java 方法。

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

因为 Clojure 是基于 Java 构建的,所以可以很容易地调用 .indexOfJava 函数。该函数返回集合中任何元素的索引,如果找不到该元素,则返回 -1。

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

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

“推荐”解决方案的问题在于,当您正在寻找的值为“零”时,它就会中断。我更喜欢这个解决方案:

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

示例用法(哪个? [123]3)或(哪个? # {123}453)

为此目的有一些方便的函数 在图珀洛图书馆。特别是,函数 contains-elem?contains-key?contains-val?非常有用。完整的文档是目前的 在 API 文件中

contains-elem?是最通用的,适用于向量或任何其他闭包:

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

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

(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值时,这些函数中的每一个都能正常工作。

另一个选择:

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

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

(.contains '(100 101 102) 101)

这么晚才发现,但这就是我在做的

(some (partial = 102) '(101 102 103))