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

1
@kotarakリンクをありがとう!コンテナーの使用に関して、私は実際にリッチに同意しますか?名前がリストまたはシーケンスに適用されたときにエラーをスローするように変更する必要があると思います
mikera

回答:


204

ああcontains?...おそらく、トップ5のFAQの1つである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))

一連のアイテムの1つを検索する場合は、より大きなセットを使用できます。検索する際にfalse/ nilは、使用することができますfalse?/ nil?-ので(#{x} x)戻りx、これ(#{nil} nil)ですnilfalseまたはの可能性がある複数のアイテムの1つを検索するnil場合は、

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

(アイテムはzipmapどのタイプのコレクションでも渡すことができることに注意してください。)


ありがとうMichal-あなたはいつものようにClojureの知恵のフォントです!この場合、私は自分の関数を書くつもりです...コア言語にまだ関数がないことに少し驚きます。
mikera

4
ミハルが言ったように-あなたが望むことをする機能がコアにすでにあります:いくつか。
コタラック

2
上記で、Michalは(some #{101} '(100 101 102))「ほとんどの場合これが機能する」とコメントしました。いつもうまくいくと言っていいのではないでしょうか?私はClojure 1.4を使用していますが、ドキュメントではこのような例を使用しています。それは私のために働き、理にかなっています。動作しない特殊なケースはありますか?
David J.

7
@DavidJames:falseまたはの存在を確認している場合は機能しませんnil-次の段落を参照してください。別の注記として、Clojure 1.5-RC1 contains?では、引数として非キーコレクションを指定すると例外がスローされます。私は、最終的なリリースが出たときにこの回答を編集すると思います。
のMichałMarczyk

1
これはバカです!コレクションの主な違いは、メンバーシップ関係です。コレクションにとって最も重要な機能だったはずです。en.wikipedia.org/wiki/Set_(mathematics)#Membership
jgomo3

132

これは同じ目的のための私の標準的なユーティリティです:

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

36
それはまたのようなfalsy値扱うように、これは、最も簡単で安全なソリューションであるnilとしますfalse。なぜこれはclojure / coreの一部ではないのですか?
Stian Soiland-Reyes 2014

2
seqcoll関数との混乱を避けるために、に名前を変更できます seqか?
ニャチャン

3
@nhaそうですね。ここでは問題ではありません。seq本文内で関数を使用していないため、同じ名前のパラメーターとの競合はありません。ただし、名前を変更するとわかりやすくなる場合は、回答を自由に編集してください。
jg-faustus 2016

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
私見これが最良の答えです。不正なクロジュールが含まれていますか?混乱を招く名前が付けられています。
mikkom 2016年

1
由緒あるマスターQc Naは彼の学生、アントンと歩いていました。アントンと、いくつかの初心者の問題を抱えて彼に話したときcontains?、QcはNaがBOで彼を打つと言った:「!。愚かな学生は、あなたが何のスプーンがありません実現しなければならない!それは下にあるすべてのちょうど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.


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)))
Rafi Panoyan 2017

6

ベクトルまたはリストがあり、その中にが含まれているかどうかを確認したい場合、それがcontains?機能しないことがわかります。ミハウはその理由をすでに説明している

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

この場合、4つのことを試すことができます。

  1. ベクトルまたはリストが本当に必要かどうかを検討してください。代わりセット使用するcontains?と、動作します。

    (contains? #{:a :b :c} :b) ; = true
  2. some次のように、ターゲットをセットでラップして使用します。

    (some #{:b} [:a :b :c]) ; = :b, which is truthy
  3. 関数として設定するショートカットは、偽の値(falseまたはnil)を検索している場合は機能しません。

    ; 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の貧弱なソリューションである理由は、1つのプロセッサでスタックを再帰的に処理するためです。より良いClojureソリューションは、<pre>(defn member?[elt col](some#(= elt%)col))</ pre>です。これは、someが利用可能なコア間で潜在的に並列であるためです。
Simon Brooke 14年

4

「list-contains?」の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、セットで使用することです-のドキュメントを参照してくださいclojure.core/some

次にsome、実際の真/偽の述語内で使用できます。

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

なぜif truefalsesometrue-ishとfalse-ishの値を既に返します。
subsub 2013年

(いくつかの#{nil} [nil])はどうですか?falseに変換されるnilを返します。
Wei Qiu 2013

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

使用例(which?[1 2 3] 3)または(which?#{1 2 3} 4 5 3)


それでも言語コアが提供する関数はありませんか?
Matanster 2016年

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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.