在Clojure中测试列表是否包含给定值的最佳方法是什么?
特别是contains?
的行为目前令我困惑:
(contains? '(100 101 102) 101) => false
我显然可以编写一个简单的函数来遍历列表并测试相等性,但肯定有一种标准的方法可以做到这一点吗?
答案 0 :(得分:191)
啊,contains?
...据说是五大常见问题之一:Clojure。
不检查集合是否包含值;它检查是否可以使用get
检索项目,换句话说,检查集合是否包含密钥。这对于集合(可以被认为不区分键和值),映射(所以(contains? {:foo 1} :foo)
是true
)和向量(但请注意(contains? [:foo :bar] 0)
是{{}有意义。 1}},因为这里的键是索引,所讨论的向量“包含”索引true
!)。
为了增加混乱,如果调用 更新:在Clojure中≥1.50
没有意义,只需返回contains?
;这就是false
以及 (contains? :foo 1)
中发生的情况。(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)
;在搜索其中一些项目中的一项时,可以nil
或false
,您可以使用
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
在这种情况下,您可以尝试以下四种方法:
考虑您是否真的需要矢量或列表。如果您使用了,则contains?
将起作用。
(contains? #{:a :b :c} :b) ; = true
使用some
,将目标包装在一个集合中,如下所示:
(some #{:b} [:a :b :c]) ; = :b, which is truthy
如果您要搜索假值(false
或nil
),则设置为功能的快捷方式将无效。
; will not work
(some #{false} [true false true]) ; = nil
在这些情况下,您应该使用内置谓词函数来表示该值,false?
或nil?
:
(some false? [true false true]) ; = true
如果你需要经常进行这种搜索,为它编写一个函数:
(defn seq-contains? [coll target] (some #(= target %) coll))
(seq-contains? [true false true] false) ; = true
另外,请参阅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)