在Clojure中,测试一个列表是否包含给定值的最佳方法是什么?
特别是,contains?
函数的行为目前让我感到困惑:
(contains? '(100 101 102) 101) => false
我显然可以编写一个简单的函数来遍历列表并测试相等性,但肯定有一种标准方法可以做到这一点吧?
在Clojure中,测试一个列表是否包含给定值的最佳方法是什么?
特别是,contains?
函数的行为目前让我感到困惑:
(contains? '(100 101 102) 101) => false
我显然可以编写一个简单的函数来遍历列表并测试相等性,但肯定有一种标准方法可以做到这一点吧?
啊,contains?
... Clojure中前五个最受欢迎的FAQ之一。
contains?
并不检查集合是否包含某个值;它检查是否可以使用get
检索到一个项,换句话说,它检查集合是否包含一个键。这对于集合(可认为在键和值之间没有区别),映射(因此(contains? {:foo 1} :foo)
为true
)和向量是有意义的(但请注意,(contains? [:foo :bar] 0)
为true
,因为这里的键是索引,而涉及的向量确实“包含”索引0
!)。
增加混淆的是,在不应调用更新:在Clojure ≥ 1.5中,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)
(请注意,任何类型的集合都可以将项目传递给 zipmap 。)
(some #{101} '(100 101 102))
时说“大多数情况下这是有效的”。难道不能说它总是有效的吗?我正在使用Clojure 1.4,文档中也使用了这种示例。它对我来说很有效并且很有意义。是否存在某些特殊情况它无法工作? - David J.false
或nil
的存在,则不起作用--请参见以下段落。另外,对于Clojure 1.5-RC1,当给定非键集合作为参数时,contains?
会抛出异常。我想等正式版发布后再编辑这个答案。 - Michał Marczyk这是我用于同一目的的标准工具:
(defn in?
"true if coll contains elm"
[coll elm]
(some #(= elm %) coll))
nil
和 false
这样的假值。那么为什么这不是 clojure/core 的一部分呢? - Stian Soiland-Reyesnil
或false
,那么这可能比(boolean (some #{elm} coll))
慢3-4倍。 - neverfox您可以始终使用 .methodName 语法调用 Java 方法。
(.contains [100 101 102] 101) => true
contains?
函数时遇到了一些初学者问题时,Qc Na 用 Bô 打了他一下,并说:“愚蠢的学生!你必须意识到没有勺子。它底层都是 Java! 使用点符号。”就在那一刻,Anton 获得了启迪。 - David Tonhofer我知道我来晚了一点,不过怎么样:
(contains? (set '(101 102 103)) 102)
在Clojure 1.4 中最终输出 true :)
(set '(101 102 103))
相当于 %{101 102 103}
。因此,你可以将答案写成 (contains? #{101 102 103} 102)
。 - David J.(101 102 103)
转换为一个集合。 - David J.(not= -1 (.indexOf '(101 102 103) 102))
这个方案可行,但下面的方法更好:
(some #(= 102 %) '(101 102 103))
这是我常用的标准工具之一,用于快速处理此类问题:
(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函数的简单方法:
(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)))
- Rafi Panoyan如果您有一个向量或列表,并想检查其中是否包含某个值,您会发现contains?
无法使用。
Michał已经解释了原因。
; does not work as you might expect
(contains? [:a :b :c] :b) ; = false
在这种情况下,您可以尝试以下四件事:
Consider whether you really need a vector or list. If you use a set instead, contains?
will work.
(contains? #{:a :b :c} :b) ; = true
Use some
, wrapping the target in a set, as follows:
(some #{:b} [:a :b :c]) ; = :b, which is truthy
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
In these cases, you should use the built-in predicate function for that value, false?
or nil?
:
(some false? [true false true]) ; = true
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
(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
可以在可用的核心上并行执行。 - Simon Brooke我在j-g-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))))