web-dev-qa-db-ja.com

リストにClojureの特定の値が含まれているかどうかをテストします

リストにClojureの特定の値が含まれているかどうかをテストする最良の方法は何ですか?

特に、contains?は現在私を混乱させています:

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

リストを走査して同等性をテストする単純な関数を作成することは明らかですが、これを行うための標準的な方法が必ずあるはずです。

150
mikera

ああ、_contains?_...おそらくトップ5のよくある質問の1つです:Clojure。

notコレクションに値が含まれているかどうかを確認します。アイテムがgetで取得できるかどうか、つまりコレクションにキーが含まれているかどうかをチェックします。これは、セット(キーと値を区別しないと考えることができる)、マップ(したがって_(contains? {:foo 1} :foo)_はtrue)、およびベクトル(ただし_(contains? [:foo :bar] 0)_はtrue、ここでのキーはインデックスであり、問​​題のベクトルはインデックス_0_!)を「含む」ためです。

混乱を増すために、_contains?_を呼び出すのが理にかなっていない場合、単にfalse;を返します。これが_(contains? :foo 1)_で起こることですおよび _(contains? '(100 101 102) 101)_。 更新: Clojure≥1.5では、意図した「キーメンバーシップ」テストをサポートしないタイプのオブジェクトを渡されると、_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に渡すことができます。)

195
Michał Marczyk

同じ目的のための私の標準ユーティリティは次のとおりです。

(defn in? 
  "true if coll contains Elm"
  [coll Elm]  
  (some #(= Elm %) coll))
121
j-g-faustus

私は少し遅れていることを知っていますが、どうですか:

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

最後にclojure 1.4ではtrueが出力されます:)

14
Giuliani Deon

.methodName構文でJavaメソッドをいつでも呼び出すことができます。

(.contains [100 101 102] 101) => true
12
Yury Litvinov
(not= -1 (.indexOf '(101 102 103) 102))

動作しますが、以下の方が優れています:

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

価値があるのは、これがリストの包含関数の単純な実装です:

(defn list-contains? [coll value]
  (let [s (seq coll)]
    (if s
      (if (= (first s) value) true (recur (rest s) value))
      false)))
7
mikera

ベクトルまたはリストがあり、その中にvalueが含まれているかどうかを確認したい場合、 contains? は機能しません。 Michałはすでに 理由の説明 です。

; 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. この種の検索を何度も行う必要がある場合は、 it の関数を作成します。

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

また、複数ターゲットがシーケンスに含まれているかどうかを確認する方法については、 Michał’s answer を参照してください。

6
Rory O'Kane

古典的な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)))
5
Simon Brooke

この目的で使用する標準ユーティリティの簡単な機能を次に示します。

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

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))))
4
Urs Reupke

セットを使用するのと同じくらい簡単です-マップと同様に、関数の位置にドロップするだけです。セット(真偽)またはnil(偽)の場合、値に評価されます。

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

実行時まで必要ない適切なサイズのベクトル/リストをチェックする場合は、set関数も使用できます。

; (def nums '(100 101 102))
((set nums) 101) ; 101
2
Brad Koch
(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)

1
Michael
(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
David

推奨される方法は、セットでsomeを使用することです-clojure.core/someのドキュメントを参照してください。

次に、本当のtrue/false述語内でsomeを使用できます。

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

この目的に便利な関数があります 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
Alan Thompson

「推奨」ソリューションの問題は、求めている値が「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
Simon Brooke

ClojureはJava上に構築されているため、.indexOf Java関数。この関数は、コレクション内の任意の要素のインデックスを返します。この要素が見つからない場合は、-1を返します。

これを利用して、簡単に言うことができます:

(not= (.indexOf [1 2 3 4] 3) -1)
=> true
0
AStanton