在Clojure中测试列表是否包含特定值

194

在Clojure中,测试一个列表是否包含给定值的最佳方法是什么?

特别是,contains?函数的行为目前让我感到困惑:

(contains? '(100 101 102) 101) => false

我显然可以编写一个简单的函数来遍历列表并测试相等性,但肯定有一种标准方法可以做到这一点吧?


7
确实有点奇怪,contains? 是 Clojure 中最具误导性的函数名称 :) 希望 Clojure 1.3 版本会将其重命名为 contains-key? 或类似名称。 - j-g-faustus
4
我认为这个问题已经被反复讨论了很多次。包含的内容不会改变。请看这里:http://groups.google.com/group/clojure/msg/f2585c149cd0465d 和 http://groups.google.com/group/clojure/msg/985478420223ecdf。 - kotarak
1
@kotarak 感谢提供链接!我实际上同意 Rich 在使用 contains? 名称方面的观点,尽管我认为当应用于列表或序列时应该更改以引发错误。 - mikera
19个回答

3

使用集合和使用map类似,只需将其放入函数位置即可。它会评估集合中的值(为真)或nil(为假)并返回结果:

(#{100 101 102} 101) ; 101
(#{100 101 102} 99) ; nil

如果你是在运行时检查一个相当大的向量/列表,你也可以使用set函数:

; (def nums '(100 101 102))
((set nums) 101) ; 101

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)


仍然没有提供此功能的语言核心函数吗? - matanster

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))))))

1
推荐的方法是使用带有集合的some - 请参阅clojure.core/some文档。
然后,您可以在真正的true/false谓词中使用some,例如:
(defn in? [coll x] (if (some #{x} coll) true false))

1
为什么要使用 iftruefalsesome 已经返回了类似于 true 和 false 的值。 - subsub
那么 (some #{nil} [nil]) 呢?它会返回 nil,这将被转换为 false。 - Wei Qiu

1
由于Clojure是基于Java构建的,您可以轻松调用Java函数.indexOf。该函数返回集合中任何元素的索引,如果找不到该元素,则返回-1。
利用这一点,我们可以简单地说:
(not= (.indexOf [1 2 3 4] 3) -1)
=> true

0
“推荐的解决方案存在问题,当你要查找的值为'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))

0

另一个选项:

((set '(100 101 102)) 101)

使用 java.util.Collection#contains() 方法:
(.contains '(100 101 102) 101)

0

发现有点晚了,但这就是我正在做的事情

(some (partial = 102) '(101 102 103)) 

0

这个目的有方便的函数在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 值时都能正常工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接