测试列表是不是包含 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!)。

为了增加混乱,在调用contains? 没有意义的情况下,它只是返回false;这就是(contains? :foo 1)(contains? '(100 101 102) 101) 中发生的情况。 更新: 在 Clojure ≥ 1.5 contains? 中,当传递一个类型的对象时抛出不支持预期的“关键成员”测试。

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

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

当搜索一堆项目中的一个时,你可以使用更大的集合;搜索false/nil时,可以使用false?/nil?——因为(#x x)返回x,因此(#nil nil)nil;当搜索其中一些可能是falsenil 的多个项目之一时,您可以使用

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

(请注意,项目可以在任何类型的集合中传递给zipmap。)

【讨论】:

正如 Michal 所说 - 核心中已经有一个功能可以满足您的需求:一些。 在上面,Michal 评论了 (some #101 '(100 101 102)) 说“大部分时间都有效”。说它总是有效是不是很公平?我正在使用 Clojure 1.4,文档使用了这种示例。它适用于我并且很有意义。是否有某种特殊情况不起作用? @DavidJames:如果您要检查falsenil 的存在,则它不起作用——请参阅以下段落。另外,在 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))

【讨论】:

这是最简单和最安全的解决方案,因为它还可以处理 nilfalse 等虚假值。现在为什么这不是 clojure/core 的一部分? seq 可以重命名为coll,以避免与函数seq 混淆? @nha 你可以这样做,是的。这里没关系:由于我们没有在body内部使用函数seq,所以与同名参数没有冲突。但是,如果您认为重命名会更容易理解,请随时编辑答案。 值得注意的是,如果您不必担心 nilfalse,这可能比 (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 truefalsesome 已经返回真值和假值。 那(一些#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
    

    如果您正在搜索虚假值(falsenil),则 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 中的特定值的主要内容,如果未能解决你的问题,请参考以下文章

如何检查数组列表是不是包含特定的字符串值? [复制]

如何在clojure中的每一行打印数字列表?

Scheme和Clojure没有原子类型谓词 - 这是设计的吗?

Clojure Core 或 Contrib 中的 Zip 函数是不是有等价物?

检查列表是不是包含大于 C# 中的值的项目 [关闭]

Clojure let vs Common Lisp let