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

在Clojure中testing列表是否包含给定值的最好方法是什么?

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

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

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

啊, contains? …据说是前五名的常见问题之一:Clojure。

它不检查集合是否包含值; 它检查一个项目是否可以通过get ,换句话说,一个集合是否包含一个key。 这是有意义的集合(可以被认为是不区分键和值),地图(所以(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? 当递交不支持预期的“关键成员资格”testing的types的对象时抛出。

做你想做的正确方法如下:

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

当search一堆物品中的一个时,你可以使用一个更大的集合; 当searchfalse / nil ,你可以使用false? / nil? – 因为(#{x} x)返回x ,因此(#{nil} nil)nil ; 当search多个项目中的一个,其中一些可能是falsenil ,你可以使用

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

(请注意,这些项目可以通过任何types的集合传递给zipmap 。)

这是我的标准util用于相同的目的:

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

我知道我稍晚一点,但是怎么样:

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

终于在clojure 1.4输出真实:)

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

作品,但下面是更好的:

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

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

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

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

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

以下是我用于此目的的标准实用程序中的一个快速function:

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

如果你有一个向量或列表,并且想要检查一个是否包含在它中,你会发现contains? 不起作用。 Michał已经解释了为什么 。

 ; 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. 如果您正在searchfalsy值( falsenil ),设置为function的快捷方式将不起作用。

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

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

     (some false? [true false true]) ; = true 
  4. 如果你需要做很多这样的search, 写一个函数

     (defn seq-contains? [coll target] (some #(= target %) coll)) (seq-contains? [true false true] false) ; = true 

此外,请参阅Michał的答案 ,以检查序列中是否包含多个目标中的任何一个。

我已经build立了jg-faustus 版本的“list-contains?”。 现在它需要任何数量的参数。

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

以下是经典的Lisp解决scheme:

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

就像使用一个集合一样简单 – 类似于地图,你可以把它放在函数的位置。 它评估的价值,如果在集合(这是truthy)或nil (这是falsey):

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

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

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

推荐的方法是使用some一套 – 见clojure.core/some文件。

然后,你可以在真正的真/假谓词中使用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)))))) 
 (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)

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

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

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

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

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

图珀洛图书馆有这方面的便利function。 特别的,这个函数contains-elem?contains-key? ,并contains-val? 非常有用。 完整的文档存在于API文档中 。

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? 按照预期的方式对集合中的现有元素和不存在的元素起作用。 对于地图,我们也可以search任何键值对(表示为len-2vector):

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

search一个集合也很简单:

  (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?更简单(和更高效) contains-key? find一个映射条目或一个设置元素:

 (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?search值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))) 

正如在testing中看到的,当searchnil值时,这些函数中的每一个都能正常工作。