Clojure에 목록에 특정 값이 포함되어 있는지 테스트


163

Clojure에 주어진 값이 목록에 포함되어 있는지 테스트하는 가장 좋은 방법은 무엇입니까?

특히의 행동 contains?은 현재 나를 혼란스럽게합니다.

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

분명히 목록을 탐색하고 평등을 테스트하는 간단한 함수를 작성할 수는 있지만 반드시 표준 방법이 있어야합니까?


7
참 이상하고 포함되어 있습니까? Clojure에서 가장 오해의 소지가있는 함수 여야합니다. 또는 유사합니다.
jg-faustus

4
나는 이것이 지금 여러 번 죽음과 대화한다고 생각합니다. 포함? 변경되지 않습니다. 여기를 참조하십시오 : groups.google.com/group/clojure/msg/f2585c149cd0465dgroups.google.com/group/clojure/msg/985478420223ecdf
kotarak

1
@kotarak 링크 주셔서 감사합니다! 실제로 포함의 사용 측면에서 Rich와 동의합니까? 이름이나 목록이나 순서에 적용될 때 오류가 발생하도록 변경되어야한다고 생각합니다
mikera

답변:


204

아, contains?... 아마도 5 가지 자주 묻는 질문 중 하나 인 Clojure입니다.

컬렉션에 값이 포함되어 있는지 확인 하지 않습니다 . 항목을 검색 할 수 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))

여러 항목 중 하나를 검색 할 때 더 큰 세트를 사용할 수 있습니다. 검색 할 때 false/ nil당신은 사용할 수 있습니다 false?/ nil?- 때문에 (#{x} x)반환 x, 따라서 (#{nil} nil)이다 nil; false또는 여러 항목 중 하나를 검색 nil할 때

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

(항목은 zipmap모든 유형의 컬렉션 으로 전달 될 수 있습니다 .)


감사합니다 Michal-당신은 항상 Clojure 지혜의 글꼴입니다! 이 경우에 내 자신의 함수를 작성하려고하는 것 같습니다 ... 핵심 언어가없는 것이 조금 놀랍습니다.
mikera

4
Michal이 말했듯이 이미 원하는 기능을 수행하는 핵심 기능이 있습니다.
kotarak

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 값처럼 취급 이것은, 간단하고 안전한 해결책 nilfalse. 왜 이것이 클로저 / 코어의 일부가 아닌가?
Stian Soiland-Reyes

2
seqcoll함수와의 혼동을 피하기 위해 로 이름을 바꿀 수 seq있습니까?
nha

3
@nha 할 수 있습니다. 예. 여기서는 중요하지 않습니다 seq. 본문 안에서 함수를 사용하지 않기 때문에 같은 이름의 매개 변수와 충돌하지 않습니다. 그러나 이름을 바꾸면 이해하기 쉽다고 생각되면 답을 자유롭게 편집하십시오.
jg-faustus

1
또는 (boolean (some #{elm} coll))걱정할 필요가없는 경우 보다 3-4 배 느려질 수 있습니다 . nilfalse
neverfox

2
@AviFlax 저는 clojure.org/guides/threading_macros 에 대해 생각하고있었습니다. "컨벤션에 따르면 시퀀스에서 작동하는 핵심 함수는 시퀀스를 마지막 인수로 예상합니다. 따라서 맵, 필터링, 제거, 축소, 포함 등을 포함하는 파이프 라인 일반적으로->> 매크로를 호출합니다. " 그러나 관례는 시퀀스와 리턴 시퀀스에서 작동하는 함수에 관한 것입니다.
John Wiseman

18

.methodName 구문을 사용하여 Java 메소드를 항상 호출 할 수 있습니다.

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

5
IMHO 이것이 최선의 답변입니다. 클로저가 너무 나쁜가요? 너무 혼란 스럽습니다.
mikkom

1
훌륭한 마스터 Qc Na는 그의 학생 Anton과 함께 걷고있었습니다. 안톤와 일부 초보자의 문제에 대해 그에게 말할 때 contains?(QC) 나은 보에 그를 명중하고 말했다 : "!. 바보 학생이 더 숟가락이없는 실현해야 ! 그것은 아래 모든 단지 자바의 점 표기법을 사용.". 그 순간 Anton은 깨달았습니다.
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

가치가있는 것은 목록에 대한 포함 함수를 간단하게 구현 한 것입니다.

(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)))
라피 Panoyan

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. 잘못된 값 ( 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의 나쁜 해결책은 하나의 프로세서에서 스택을 되풀이하기 때문입니다. 더 나은 Clojure 솔루션은 <pre> (defn member? [elt col] (일부 # (= elt %) col)) </ pre>입니다. 이는 some사용 가능한 코어에서 잠재적으로 병렬 이기 때문 입니다.
Simon Brooke

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

권장되는 방법은 someset-see documentation과 함께 사용 하는 것입니다 clojure.core/some.

그런 다음 some실제 true / false 술어 내에서 사용할 수 있습니다 (예 :

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

if true그리고 false? some이미 참과 거짓 값을 반환합니다.
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를 기반으로하므로 .indexOfJava 함수 를 쉽게 호출 할 수 있습니다 . 이 함수는 컬렉션에있는 요소의 인덱스를 반환하고이 요소를 찾을 수 없으면 -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?가장 일반적이며 벡터 또는 다른 클로저를 대상으로합니다 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.