测试列表是否在Clojure中包含给定值的最佳方法是什么?
特别是,contains?
目前的行为使我感到困惑:
(contains? '(100 101 102) 101) => false
很明显,我可以编写一个简单的函数来遍历列表并测试是否相等,但是一定要有一种标准的方法吗?
测试列表是否在Clojure中包含给定值的最佳方法是什么?
特别是,contains?
目前的行为使我感到困惑:
(contains? '(100 101 102) 101) => false
很明显,我可以编写一个简单的函数来遍历列表并测试是否相等,但是一定要有一种标准的方法吗?
Answers:
啊,contains?
...据说是最常见的五个常见问题之一:Clojure。
它不检查一个集合是否包含一个值; 它检查是否可以通过检索项目,get
或者换句话说,集合是否包含关键字。这使得套(可看作使得键和值之间没有区别),地图(所以感觉(contains? {:foo 1} :foo)
是true
)和载体(但要注意(contains? [:foo :bar] 0)
是true
,由于按键这里有指标,问题中的载体并“包含”了索引0
!)。
更令人困惑的是,在无法合理调用的情况下 更新:在Clojure中contains?
,它只是返回false
;这是什么情况(contains? :foo 1)
,也 (contains? '(100 101 102) 101)
。contains?
,处理不支持预期的“密钥成员身份”测试的类型的对象时,抛出1.5 以上的错误。
要做您想做的正确方法如下:
; 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)
(请注意,这些项目可以传递到zipmap
任何类型的集合中。)
(some #{101} '(100 101 102))
说“大多数情况下这是可行的”。说它总是有效不是公平的吗?我正在使用Clojure 1.4,文档使用了这种示例。它对我有用,很有意义。是否存在某种无法使用的特殊情况?
false
或nil
- 是否存在,则不起作用。请参阅以下段落。另外,在Clojure 1.5-RC1中contains?
,给定非键集合作为参数时,将引发异常。我想我将在最终版本发布时编辑此答案。
这是出于相同目的的标准工具:
(defn in?
"true if coll contains elm"
[coll elm]
(some #(= elm %) coll))
nil
和的虚假值false
。现在为什么这不是clojure / core的一部分?
seq
可以将其重命名为coll
,以避免与功能混淆 seq
?
seq
体内使用函数,因此与同名参数没有冲突。但是,如果您认为重命名将使其更容易理解,请随时编辑答案。
(boolean (some #{elm} coll))
不需担心nil
或的情况相比,这样做的速度可以慢3-4倍false
。
您始终可以使用.methodName语法调用Java方法。
(.contains [100 101 102] 101) => true
contains?
,纳克(Qc Na)用博达(Bô)打他,说:“愚蠢的学生!您必须意识到没有汤匙。下面全是Java!使用点号表示。”。在那一刻,安东开悟了。
对于它的价值,这是我对列表的contains函数的简单实现:
(defn list-contains? [coll value]
(let [s (seq coll)]
(if s
(if (= (first s) value) true (recur (rest s) value))
false)))
(defn list-contains? [pred coll value] (let [s (seq coll)] (if s (if (pred (first s) value) true (recur (rest s) value)) false)))
如果您有一个向量或列表,并且想要检查其中是否包含值,则会发现它contains?
不起作用。Michał已经解释了原因。
; 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ł的答案,以查看序列中是否包含多个目标。
这是经典的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)))
some
在可用核被潜在地平行。
它就像使用集合一样简单-与地图类似,您可以将其放在函数位置。如果在集合中(真)或nil
(假),则求值:
(#{100 101 102} 101) ; 101
(#{100 101 102} 99) ; nil
如果要检查的是大小合理的向量/列表,直到运行时才可以使用,也可以使用以下set
函数:
; (def nums '(100 101 102))
((set nums) 101) ; 101
(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)
Tupelo库中有用于此目的的便捷功能。尤其是功能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?
集合中现有和不存在的元素均按预期工作。对于地图,我们还可以搜索任何键值对(表示为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
值时都可以正常工作。