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


162

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

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

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

很明显,我可以编写一个简单的函数来遍历列表并测试是否相等,但是一定要有一种标准的方法吗?


7
确实很奇怪,包含吗?必须是Clojure中最易误解的函数:)希望Clojure 1.3将其重命名为contains-key吗?或类似。
jg-faustus 2010年

4
我认为这已经被谈论多次了。包含?不会改变。请参阅此处:groups.google.com/group/clojure/msg/f2585c149cd0465dgroups.google.com/group/clojure/msg/985478420223ecdf
kotarak 2010年

1
@kotarak感谢您的链接!我实际上同意Rich在这里使用容器吗?名称,尽管我认为应将其更改为应用于列表或序列时会引发错误
mikera 2010年

Answers:


204

啊,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中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任何类型的集合中。)


谢谢Michal-您像往常一样是Clojure智慧的字体!在这种情况下,我似乎要编写自己的函数...令我感到惊讶的是,核心语言中还没有一个函数。
mikera 2010年

4
正如Michal所说-核心中已经有一个功能可以满足您的需求:一些。
kotarak 2010年

2
上面,Michal评论(some #{101} '(100 101 102))说“大多数情况下这是可行的”。说它总是有效不是公平的吗?我正在使用Clojure 1.4,文档使用了这种示例。它对我有用,很有意义。是否存在某种无法使用的特殊情况?
David J.

7
@DavidJames:如果您正在检查falsenil- 是否存在,则不起作用。请参阅以下段落。另外,在Clojure 1.5-RC1中contains?,给定非键集合作为参数时,将引发异常。我想我将在最终版本发布时编辑此答案。
米哈尔Marczyk

1
真蠢!集合的主要区别是成员关系。它应该是集合的最重要功能。en.wikipedia.org/wiki/Set_(mathematics)#Membership
jgomo3 '18

132

这是出于相同目的的标准工具:

(defn in? 
  "true if coll contains elm"
  [coll elm]  
  (some #(= elm %) coll))

36
这是最简单,最安全的解决方案,因为它也可以处理诸如nil和的虚假值false。现在为什么这不是clojure / core的一部分?
Stian Soiland-Reyes 2014年

2
seq可以将其重命名为coll,以避免与功能混淆 seq
nha 2016年

3
@nha你可以那样做,是的。在这里无关紧要:由于我们不在seq体内使用函数,因此与同名参数没有冲突。但是,如果您认为重命名将使其更容易理解,请随时编辑答案。
jg-faustus

1
值得注意的是,与(boolean (some #{elm} coll))不需担心nil或的情况相比,这样做的速度可以慢3-4倍false
neverfox

2
@AviFlax我在考虑clojure.org/guides/threading_macros,其中说:“按照惯例,对序列进行操作的核心函数会将序列作为最后一个参数。因此,包含map,filter,remove,reduce,into等的管道通常需要->>宏。” 但是我猜想约定更多是关于对序列和返回序列进行操作的函数。
约翰·怀斯曼

18

您始终可以使用.methodName语法调用Java方法。

(.contains [100 101 102] 101) => true

5
恕我直言,这是最好的答案。Clojure包含的东西太差了吗?如此混乱的命名。
mikkom

1
尊敬的大师Qc Na和他的学生Anton一起散步。当安东(Anton)告诉他有一些初学者的问题时contains?,纳克(Qc Na)用博达(Bô)打他,说:“愚蠢的学生!您必须意识到没有汤匙。下面全是Java!使用点号表示。”。在那一刻,安东开悟了。
David Tonhofer

17

我知道我来晚了一点,但是呢:

(contains? (set '(101 102 103)) 102)

最后在Clojure 1.4中输出true :)


3
(set '(101 102 103))与相同%{101 102 103}。因此,您的答案可以写成(contains? #{101 102 103} 102)
David J.

4
这具有需要将原始列表转换为'(101 102 103)集合的缺点。
David J.

12
(not= -1 (.indexOf '(101 102 103) 102))

可以,但是下面更好:

(some #(= 102 %) '(101 102 103)) 

7

对于它的价值,这是我对列表的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)))
拉菲·帕诺扬

6

如果您有一个向量或列表,并且想要检查其中是否包含,则会发现它contains?不起作用。Michał已经解释了原因

; does not work as you might expect
(contains? [:a :b :c] :b) ; = false

在这种情况下,您可以尝试四种方法:

  1. 考虑您是否真的需要向量或列表。如果您使用一组contains?则可以使用。

    (contains? #{:a :b :c} :b) ; = true
  2. 使用some,将目标包装在一组中,如下所示:

    (some #{:b} [:a :b :c]) ; = :b, which is truthy
  3. 如果要搜索伪造的值(falsenil),则功能设置快捷方式将不起作用。

    ; will not work
    (some #{false} [true false true]) ; = nil

    在这些情况下,您应该使用内置谓词函数获取该值,false?或者nil?

    (some false? [true false true]) ; = true
  4. 如果您将需要进行大量此类搜索,请为其编写一个函数

    (defn seq-contains? [coll target] (some #(= target %) coll))
    (seq-contains? [true false true] false) ; = true

另外,请参阅Michał的答案,以查看序列中是否包含多个目标。


5

这是我用于此目的的标准实用工具中的一个快速功能:

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

是的,您的优点是,一旦找到匹配项,它将立即停止,而不是继续映射整个序列。
G__

5

这是经典的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)))

4
好的,在Clojure中解决方案差的原因是它在一个处理器上递归了堆栈。一个更好的Clojure溶液<PRE>(DEFN构件?[ELT山口](一些#(= ELT%)列))</ PRE>这是因为some在可用核被潜在地平行。
Simon Brooke 2014年

4

我基于jg-faustus 版本的“列表包含?”。现在需要任何数量的参数。

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

2

它就像使用集合一样简单-与地图类似,您可以将其放在函数位置。如果在集合中(真)或nil(假),则求值:

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

如果要检查的是大小合理的向量/列表,直到运行时才可以使用,也可以使用以下set函数:

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

1

推荐的方法是some与set-see文档一起使用clojure.core/some

然后,您可以some在真实的true / false谓词中使用,例如

(defn in? [coll x] (if (some #{x} coll) true false))

为什么if truefalsesome已经返回了真假值。
subsub

那(一些#{nil} [nil])呢?它会返回nil并将其转换为false。
魏秋

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

由于Clojure是基于Java构建的,因此您可以轻松地调用 .indexOf Java函数。此函数返回集合中任何元素的索引,如果找不到该元素,则返回-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

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值时都可以正常工作。


0

另外的选择:

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

使用java.util.Collection#contains():

(.contains '(100 101 102) 101)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.