Programing

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

lottogame 2020. 6. 9. 07:41
반응형

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


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

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

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

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


아, 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모든 유형의 컬렉션 으로 전달 될 수 있습니다 .)


동일한 목적을위한 표준 유틸리티는 다음과 같습니다.

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

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

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

벡터 또는 목록이 있고 이 포함되어 있는지 확인하려는 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ł의 답변참조하십시오 .


이 목적으로 사용하는 표준 유틸리티 중 빠른 기능이 있습니다.

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

다음은 클래식 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)))

jg-faustus 버전 의 "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))))

It is as simple as using a set - similar to maps, you can just drop it in the function position. It evaluates to the value if in the set (which is truthy) or nil (which is falsey):

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

If you're checking against a reasonably sized vector/list you won't have until runtime, you can also use the set function:

; (def nums '(100 101 102))
((set nums) 101) ; 101

The recommended way is to use some with a set - see documentation for clojure.core/some.

You could then use some within a real true/false predicate, e.g.

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

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

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

example usage (which? [ 1 2 3 ] 3) or (which? #{ 1 2 3} 4 5 3)


The problem with the 'recommended' solution is it is breaks when the value you are seeking is 'nil'. I prefer this solution:

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

There are convenient functions for this purpose in the Tupelo library. In particular, the functions contains-elem?, contains-key?, and contains-val? are very useful. Full documentation is present in the API docs.

contains-elem? is the most generic and is intended for vectors or any other 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))))

Here we see that for an integer range or a mixed vector, contains-elem? works as expected for both existing and non-existant elements in the collection. For maps, we can also search for any key-value pair (expressed as a len-2 vector):

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

It is also straightforward to search a set:

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

For maps & sets, it is simpler (& more efficient) to use contains-key? to find a map entry or a set element:

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

And, for maps, you can also search for values with 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)))

As seen in the test, each of these functions works correctly when for searching for nil values.


Since Clojure is built on Java, you can just as easily call the .indexOf Java function. This function returns the index of any element in a collection, and if it can't find this element, returns -1.

Making use of this we could simply say:

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

참고URL : https://stackoverflow.com/questions/3249334/test-whether-a-list-contains-a-specific-value-in-clojure

반응형