リストにClojureの特定の値が含まれているかどうかをテストする最良の方法は何ですか?
特に、contains?
は現在私を混乱させています:
(contains? '(100 101 102) 101) => false
リストを走査して同等性をテストする単純な関数を作成することは明らかですが、これを行うための標準的な方法が必ずあるはずです。
ああ、_contains?
_...おそらくトップ5のよくある質問の1つです:Clojure。
notコレクションに値が含まれているかどうかを確認します。アイテムがget
で取得できるかどうか、つまりコレクションにキーが含まれているかどうかをチェックします。これは、セット(キーと値を区別しないと考えることができる)、マップ(したがって_(contains? {:foo 1} :foo)
_はtrue
)、およびベクトル(ただし_(contains? [:foo :bar] 0)
_はtrue
、ここでのキーはインデックスであり、問題のベクトルはインデックス_0
_!)を「含む」ためです。
混乱を増すために、_ 更新: Clojure≥1.5では、意図した「キーメンバーシップ」テストをサポートしないタイプのオブジェクトを渡されると、_contains?
_を呼び出すのが理にかなっていない場合、単にfalse
;を返します。これが_(contains? :foo 1)
_で起こることですおよび _(contains? '(100 101 102) 101)
_。contains?
_がスローされます。
あなたがしようとしていることを行う正しい方法は次のとおりです:
_; most of the time this works
(some #{101} '(100 101 102))
_
多数のアイテムの1つを検索する場合、より大きなセットを使用できます。 false
/nil
を検索する場合、_false?
_/_nil?
_を使用できます-_(#{x} x)
_はx
を返すため、_(#{nil} nil)
_ is nil
;いくつかがfalse
またはnil
である可能性のある複数のアイテムの1つを検索する場合、次を使用できます。
_(some (zipmap [...the items...] (repeat true)) the-collection)
_
(項目は、どのタイプのコレクションでもzipmap
に渡すことができます。)
同じ目的のための私の標準ユーティリティは次のとおりです。
(defn in?
"true if coll contains Elm"
[coll Elm]
(some #(= Elm %) coll))
私は少し遅れていることを知っていますが、どうですか:
(contains? (set '(101 102 103)) 102)
最後にclojure 1.4ではtrueが出力されます:)
.methodName構文でJavaメソッドをいつでも呼び出すことができます。
(.contains [100 101 102] 101) => true
(not= -1 (.indexOf '(101 102 103) 102))
動作しますが、以下の方が優れています:
(some #(= 102 %) '(101 102 103))
価値があるのは、これがリストの包含関数の単純な実装です:
(defn list-contains? [coll value]
(let [s (seq coll)]
(if s
(if (= (first s) value) true (recur (rest s) value))
false)))
ベクトルまたはリストがあり、その中にvalueが含まれているかどうかを確認したい場合、 contains?
は機能しません。 Michałはすでに 理由の説明 です。
; does not work as you might expect
(contains? [:a :b :c] :b) ; = false
この場合、次の4つのことを試すことができます。
本当にベクターまたはリストが必要かどうかを検討してください。 代わりにセットを使用する場合、contains?
動作します。
(contains? #{:a :b :c} :b) ; = true
次のように some
を使用して、ターゲットをセットにラップします。
(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
この種の検索を何度も行う必要がある場合は、 it の関数を作成します。
(defn seq-contains? [coll target] (some #(= target %) coll))
(seq-contains? [true false true] false) ; = true
また、複数ターゲットがシーケンスに含まれているかどうかを確認する方法については、 Michał’s answer を参照してください。
古典的な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)))
この目的で使用する標準ユーティリティの簡単な機能を次に示します。
(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))))
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))))
セットを使用するのと同じくらい簡単です-マップと同様に、関数の位置にドロップするだけです。セット(真偽)またはnil
(偽)の場合、値に評価されます。
(#{100 101 102} 101) ; 101
(#{100 101 102} 99) ; nil
実行時まで必要ない適切なサイズのベクトル/リストをチェックする場合は、set
関数も使用できます。
; (def nums '(100 101 102))
((set nums) 101) ; 101
(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)
(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))))))
推奨される方法は、セットでsome
を使用することです-clojure.core/some
のドキュメントを参照してください。
次に、本当のtrue/false述語内でsome
を使用できます。
(defn in? [coll x] (if (some #{x} coll) true false))
この目的に便利な関数があります 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
値を検索する場合に正しく機能します。
「推奨」ソリューションの問題は、求めている値が「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))
ClojureはJava上に構築されているため、.indexOf
Java関数。この関数は、コレクション内の任意の要素のインデックスを返します。この要素が見つからない場合は、-1を返します。
これを利用して、簡単に言うことができます:
(not= (.indexOf [1 2 3 4] 3) -1)
=> true