Clojureで指定された値がリストに含まれているかどうかをテストする最良の方法は何ですか?
特に、の動作contains?は現在私を混乱させています:
(contains? '(100 101 102) 101) => false
リストを走査して同等かどうかをテストする簡単な関数を書くことはできますが、これを行うための標準的な方法があるはずです。
Clojureで指定された値がリストに含まれているかどうかをテストする最良の方法は何ですか?
特に、の動作contains?は現在私を混乱させています:
(contains? '(100 101 102) 101) => false
リストを走査して同等かどうかをテストする簡単な関数を書くことはできますが、これを行うための標準的な方法があるはずです。
回答:
ああcontains?...おそらく、トップ5のFAQの1つであるClojureです。
コレクションに値が含まれているかどうかはチェックしません。アイテムを取得できるかどうか、getつまりコレクションにキーが含まれているかどうかを確認します。これは、(キーと値の間に区別を作っていないと考えることができます)のセットのために理にかなってマッピングし(そう(contains? {:foo 1} :foo)であるtrue)とベクトル(しかしノート(contains? [:foo :bar] 0)でtrue、ここでの鍵は、問題のインデックスとのベクトルであるためには、「含まれて」んインデックス0!)。
混乱を増すために、を呼び出すのが意味をなさない場合は 更新: Clojure contains?、単にを返しfalseます。これは中に何が起こるかである(contains? :foo 1) とも (contains? '(100 101 102) 101)。contains?では、意図された「キーメンバーシップ」テストをサポートしていないタイプのオブジェクトを渡すと、1.5以上のスローが発生します。
あなたがしようとしていることを行う正しい方法は次のとおりです:
; most of the time this works
(some #{101} '(100 101 102))
一連のアイテムの1つを検索する場合は、より大きなセットを使用できます。検索する際にfalse/ nilは、使用することができますfalse?/ nil?-ので(#{x} x)戻りx、これ(#{nil} nil)ですnil。falseまたはの可能性がある複数のアイテムの1つを検索するnil場合は、
(some (zipmap [...the items...] (repeat true)) the-collection)
(アイテムはzipmapどのタイプのコレクションでも渡すことができることに注意してください。)
(some #{101} '(100 101 102))「ほとんどの場合これが機能する」とコメントしました。いつもうまくいくと言っていいのではないでしょうか?私はClojure 1.4を使用していますが、ドキュメントではこのような例を使用しています。それは私のために働き、理にかなっています。動作しない特殊なケースはありますか?
falseまたはの存在を確認している場合は機能しませんnil-次の段落を参照してください。別の注記として、Clojure 1.5-RC1 contains?では、引数として非キーコレクションを指定すると例外がスローされます。私は、最終的なリリースが出たときにこの回答を編集すると思います。
これは同じ目的のための私の標準的なユーティリティです:
(defn in?
"true if coll contains elm"
[coll elm]
(some #(= elm %) coll))
nilとしますfalse。なぜこれはclojure / coreの一部ではないのですか?
seqcoll関数との混乱を避けるために、に名前を変更できます seqか?
seq本文内で関数を使用していないため、同じ名前のパラメーターとの競合はありません。ただし、名前を変更するとわかりやすくなる場合は、回答を自由に編集してください。
(boolean (some #{elm} coll))、nilまたはを心配する必要がない場合よりも3〜4倍遅くなることに注意してくださいfalse。
いつでも.methodName構文でJavaメソッドを呼び出すことができます。
(.contains [100 101 102] 101) => true
contains?、QcはNaがBOで彼を打つと言った:「!。愚かな学生は、あなたが何のスプーンがありません実現しなければならない!それは下にあるすべてのちょうどJavaのドット表記を使用します。」。その瞬間、アントンは悟りを開いた。
価値があるのは、これがリストの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)))
ベクトルまたはリストがあり、その中に値が含まれているかどうかを確認したい場合、それがcontains?機能しないことがわかります。ミハウはその理由をすでに説明している。
; does not work as you might expect
(contains? [:a :b :c] :b) ; = false
この場合、4つのことを試すことができます。
ベクトルまたはリストが本当に必要かどうかを検討してください。代わりにセットを使用するcontains?と、動作します。
(contains? #{:a :b :c} :b) ; = truesome次のように、ターゲットをセットでラップして使用します。
(some #{:b} [:a :b :c]) ; = :b, which is truthy関数として設定するショートカットは、偽の値(falseまたはnil)を検索している場合は機能しません。
; 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łの回答を参照してください。
これが古典的な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)))
someが利用可能なコア間で潜在的に並列であるためです。
「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))))
推奨される方法はsome、セットで使用することです-のドキュメントを参照してくださいclojure.core/some。
次にsome、実際の真/偽の述語内で使用できます。
(defn in? [coll x] (if (some #{x} coll) true false))
if trueとfalse?sometrue-ishとfalse-ishの値を既に返します。
(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)
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値を検索するときに正しく機能します。