测试列表是不是包含 Clojure 中的特定值
Posted
技术标签:
【中文标题】测试列表是不是包含 Clojure 中的特定值【英文标题】:Test whether a list contains a specific value in Clojure测试列表是否包含 Clojure 中的特定值 【发布时间】:2011-03-16 00:40:11 【问题描述】:在 Clojure 中测试列表是否包含给定值的最佳方法是什么?
尤其是contains?
的行为目前让我很困惑:
(contains? '(100 101 102) 101) => false
我显然可以编写一个简单的函数来遍历列表并测试是否相等,但肯定有一个标准的方法来做到这一点?
【问题讨论】:
确实很奇怪,包含?必须是 Clojure 中最容易被误导的函数 :) 希望 Clojure 1.3 能看到它重命名为 contains-key?或类似的。 我想这已经被说死了好几次了。包含?不会改变。见这里:groups.google.com/group/clojure/msg/f2585c149cd0465d 和 groups.google.com/group/clojure/msg/985478420223ecdf @kotarak 感谢您的链接!在使用包含的方面,我实际上同意 Rich 的观点?名称虽然我认为应该更改它以在应用于列表或序列时引发错误 【参考方案1】:啊,contains?
...据说是前五个常见问题解答之一:Clojure。
它不检查一个集合是否包含一个值;它会检查是否可以使用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
。)
【讨论】:
正如 Michal 所说 - 核心中已经有一个功能可以满足您的需求:一些。 在上面,Michal 评论了(some #101 '(100 101 102))
说“大部分时间都有效”。说它总是有效是不是很公平?我正在使用 Clojure 1.4,文档使用了这种示例。它适用于我并且很有意义。是否有某种特殊情况不起作用?
@DavidJames:如果您要检查false
或nil
的存在,则它不起作用——请参阅以下段落。另外,在 Clojure 1.5-RC1 中,contains?
在给定非键控集合作为参数时会引发异常。我想我会在最终版本发布时编辑这个答案。
这太愚蠢了!集合的主要区别是成员关系。它应该是收藏品最重要的功能。 en.wikipedia.org/wiki/Set_(mathematics)#Membership
@jgomo3 你可以使用包含吗?在一组测试成员资格。你不能做的是在地图或列表上使用它,因为这样做不再是 O(1),而是变成了 O(n)。包含?意味着 O(1),这就是为什么你需要在这些情况下进行自己的线性搜索。【参考方案2】:
这是我用于此目的的标准实用程序中的一个快速功能:
(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))))
【讨论】:
是的,你的优点是一旦找到匹配项就会停止,而不是继续映射整个序列。【参考方案3】:不管怎样,这是我对列表的 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)))
【参考方案4】:
这是我用于相同目的的标准工具:
(defn in?
"true if coll contains elm"
[coll elm]
(some #(= elm %) coll))
【讨论】:
这是最简单和最安全的解决方案,因为它还可以处理nil
和 false
等虚假值。现在为什么这不是 clojure/core 的一部分?
seq
可以重命名为coll
,以避免与函数seq
混淆?
@nha 你可以这样做,是的。这里没关系:由于我们没有在body内部使用函数seq
,所以与同名参数没有冲突。但是,如果您认为重命名会更容易理解,请随时编辑答案。
值得注意的是,如果您不必担心 nil
或 false
,这可能比 (boolean (some #elm coll))
慢 3-4 倍。
@AviFlax 我在想clojure.org/guides/threading_macros,它说“按照惯例,对序列进行操作的核心函数期望序列作为它们的最后一个参数。因此,包含 map、filter、remove、reduce 的管道, into 等通常需要 ->> 宏。”但我猜这个约定更多是关于对序列进行操作并返回序列的函数。【参考方案5】:
我建立在 j-g-faustus version 的 "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))))
【讨论】:
【参考方案6】:我知道我有点晚了,但是呢:
(contains? (set '(101 102 103)) 102)
最后在 clojure 1.4 中输出 true :)
【讨论】:
(set '(101 102 103))
与 %101 102 103
相同。所以你的答案可以写成(contains? #101 102 103 102)
。
这样做的缺点是需要将原始列表'(101 102 103)
转换为集合。【参考方案7】:
(not= -1 (.indexOf '(101 102 103) 102))
有效,但以下更好:
(some #(= 102 %) '(101 102 103))
【讨论】:
如果没有匹配项,有些返回 nil,而不是 false【参考方案8】:推荐的方法是使用 some
和一组 - 请参阅 clojure.core/some
的文档。
然后您可以在真正的真/假谓词中使用some
,例如
(defn in? [coll x] (if (some #x coll) true false))
【讨论】:
为什么是if
true
和 false
? some
已经返回真值和假值。
那(一些#nil [nil])呢?它会返回 nil 并将其转换为 false。【参考方案9】:
(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))))))
【讨论】:
【参考方案10】:如果你有一个向量或列表,想检查其中是否包含一个值,你会发现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
),则 set-as-function 快捷方式将不起作用。
; 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,了解检查序列中是否包含任何多个目标的方法。
【讨论】:
【参考方案11】:这是经典的 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)))
【讨论】:
好的,Clojure 中一个糟糕的解决方案的原因是它在一个处理器上递归堆栈。更好的 Clojure 解决方案是(defn member? [elt col] (some #(= elt %) col))这是因为
some
可能跨可用内核并行。【参考方案12】:
就像使用一个集合一样简单 - 类似于地图,您只需将其放在函数位置即可。如果在集合中(这是真的)或nil
(这是假的),它会评估值:
(#100 101 102 101) ; 101
(#100 101 102 99) ; nil
如果您要检查一个合理大小的向量/列表,直到运行时才拥有,您还可以使用 set
函数:
; (def nums '(100 101 102))
((set nums) 101) ; 101
【讨论】:
【参考方案13】:您始终可以使用 .methodName 语法调用 java 方法。
(.contains [100 101 102] 101) => true
【讨论】:
恕我直言,这是最好的答案。太糟糕的clojure包含?名字太混乱了。 尊贵的 Qc Na 大师与他的学生 Anton 同行。当 Anton 告诉他contains?
有一些初学者的问题时,Qc Na 用 Bô 打他说:“愚蠢的学生!你必须意识到没有勺子。下面都是 Java!使用点符号。”。那一刻,安东顿悟了。【参考方案14】:
由于 Clojure 是基于 Java 构建的,因此您可以很容易地调用 .indexOf
Java 函数。该函数返回集合中任意元素的索引,如果找不到该元素,则返回-1。
利用这一点,我们可以简单地说:
(not= (.indexOf [1 2 3 4] 3) -1)
=> true
【讨论】:
【参考方案15】:“推荐”解决方案的问题是,当您寻求的值为“nil”时,它会中断。我更喜欢这个解决方案:
(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】:(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 )))
示例用法 (which? [ 1 2 3 ] 3) 或 (which? # 1 2 3 4 5 3)
【讨论】:
仍然没有为它提供语言核心功能?【参考方案17】:为此目的提供了方便的功能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
值时都能正常工作。
【讨论】:
【参考方案18】:另一种选择:
((set '(100 101 102)) 101)
使用 java.util.Collection#contains():
(.contains '(100 101 102) 101)
【讨论】:
以上是关于测试列表是不是包含 Clojure 中的特定值的主要内容,如果未能解决你的问题,请参考以下文章
Scheme和Clojure没有原子类型谓词 - 这是设计的吗?