Sprawdź, czy lista zawiera określoną wartość w Clojure

Jaki jest najlepszy sposób na sprawdzenie, czy lista zawiera daną wartość w Clojure?

W szczególności zachowanie contains? obecnie mnie myli:

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

Mógłbym oczywiście napisać prostą funkcję, aby przejść przez Listę i sprawdzić równość, ale musi być na pewno standardowy sposób, aby to zrobić?

Author: Micah Elliott, 2010-07-14

17 answers

Ah, contains?... podobno jeden z pięciu najczęściej zadawanych pytań re: Clojure.

Sprawdza Nie , czy zbiór zawiera wartość; sprawdza, czy Element można pobrać za pomocą get lub innymi słowy, czy zbiór zawiera klucz. Ma to sens dla zbiorów (które można uznać za nie rozróżniające kluczy od wartości), map (więc (contains? {:foo 1} :foo) jest true) i wektorów (ale zauważ, że (contains? [:foo :bar] 0) jest true, ponieważ klucze tutaj są indeksami i wektorem, o którym mowa czy "zawiera" indeks 0!).

aby dodać do zamieszania, w przypadkach, gdy nie ma sensu wywoływać contains?, po prostu zwraca false; tak się dzieje w (contains? :foo 1) a także (contains? '(100 101 102) 101). Update: W Clojure ≥ 1.5 contains? rzuca po podaniu obiektu typu, który nie obsługuje zamierzonego testu "członkostwa klucza".

Prawidłowy sposób, aby zrobić to, co próbujesz zrobić, jest następujący:

; most of the time this works
(some #{101} '(100 101 102))

Podczas wyszukiwania jednego z kilku przedmiotów, można użyć większego zestawu; szukając false / nil, możesz użyć false? / nil? -- ponieważ (#{x} x) zwraca x, więc (#{nil} nil) jest nil; szukając jednego z wielu elementów, z których niektóre mogą być false lub nil, możesz użyć

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

(Należy pamiętać, że elementy mogą być przekazywane do zipmap w dowolnym typie kolekcji.)

 179
Author: Michał Marczyk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-07 17:47:51

Oto mój standardowy util do tego samego celu:

(defn in? 
  "true if coll contains elm"
  [coll elm]  
  (some #(= elm %) coll))
 117
Author: j-g-faustus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-23 08:43:18

Wiem, że jestem trochę później, ale co z:

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

Nareszcie w clojure 1.4 wyjście true:)

 13
Author: Giuliani Sanches,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-19 03:01:45
(not= -1 (.indexOf '(101 102 103) 102))

Działa, ale poniżej jest lepiej:

(some #(= 102 %) '(101 102 103)) 
 10
Author: jamesqiu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-15 20:59:56

Zawsze można wywołać metody Javy z .składnia methodName.

(.contains [100 101 102] 101) => true
 9
Author: Yury Litvinov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-19 16:51:19

Jeśli to coś warte, to jest to moja prosta implementacja funkcji contains dla List:

(defn list-contains? [coll value]
  (let [s (seq coll)]
    (if s
      (if (= (first s) value) true (recur (rest s) value))
      false)))
 7
Author: mikera,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-14 19:21:51

Jeśli masz wektor lub listę i chcesz sprawdzić, czy zawiera się w niej wartość , przekonasz się, że contains? nie działa. Michał już wyjaśnił dlaczego .

; does not work as you might expect
(contains? [:a :b :c] :b) ; = false

Są cztery rzeczy, które możesz wypróbować w tym przypadku:

  1. Zastanów się, czy naprawdę potrzebujesz wektora czy listy. Jeśli zamiast tego używasz zestawu, contains? zadziała.

    (contains? #{:a :b :c} :b) ; = true
    
  2. Użycie some, owijanie celu w set, jak następuje:

    (some #{:b} [:a :b :c]) ; = :b, which is truthy
    
  3. Skrót set-as-function nie będzie działał, jeśli szukasz fałszywej wartości (false lub nil).

    ; will not work
    (some #{false} [true false true]) ; = nil
    

    W takich przypadkach należy użyć wbudowanej funkcji predykatu dla tej wartości, false? lub nil?:

    (some false? [true false true]) ; = true
    
  4. Jeśli będziesz musiał często wykonywać tego rodzaju wyszukiwanie, napisz dla niego funkcję :

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

Zobacz też odpowiedź Michała dla sposoby sprawdzania, czy któryś z wielu celów jest zawarty w sekwencji.

 6
Author: Rory O'Kane,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:47

Oto szybka funkcja z moich standardowych narzędzi, które używam do tego celu:

(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
Author: G__,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-14 19:19:18

Oto klasyczne rozwiązanie Lispu:

(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
Author: Simon Brooke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-25 11:23:47

Zbudowałem na j-g-faustusie Wersja z "list-contains?". Teraz przyjmuje dowolną liczbę argumentów.

(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
Author: Urs Reupke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:41

Jest to tak proste, jak użycie zestawu-podobnego do map, można go po prostu upuścić w pozycji funkcji. Zwraca wartość if w zbiorze (co jest prawdziwe) lub nil (co jest fałszywe):

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

Jeśli sprawdzasz wektor/Listę o rozsądnej wielkości, możesz użyć również funkcji set:

; (def nums '(100 101 102))
((set nums) 101) ; 101
 2
Author: Brad Koch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-28 17:32:11

Zalecanym sposobem jest użycie some z zestawem-patrz dokumentacja dla clojure.core/some.

Można wtedy użyć some w ramach prawdziwego orzeczenia true / false, np.

(defn in? [coll x] (if (some #{x} coll) true false))
 1
Author: KingCode,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-16 17:04:40
(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
Author: David,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-15 21:20:55
(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 )))

Przykładowe użycie (które? [ 1 2 3] 3) lub (które? #{ 1 2 3} 4 5 3)

 1
Author: Michael,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-18 23:04:38

Ponieważ Clojure jest zbudowany na Javie, możesz równie łatwo wywołać funkcję .indexOf Java. Funkcja zwraca indeks dowolnego elementu w kolekcji, a jeśli nie może znaleźć tego elementu, zwraca -1.

Korzystając z tego możemy po prostu powiedzieć:

    (not= (.indexOf [1 2 3 4] 3) -1)
    => true
 0
Author: AStanton,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-27 15:30:06

Problem z "zalecanym" rozwiązaniem polega na tym, że łamie się, gdy szukana wartość jest "nil". Wolę takie rozwiązanie:

(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
Author: Simon Brooke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-29 10:31:40

Istnieją wygodne funkcje do tego celu w Bibliotece Tupelo . W szczególności funkcje contains-elem?, contains-key?, i contains-val? są bardzo przydatne. Pełna dokumentacja znajduje się w dokumentach API .

contains-elem? jest najbardziej ogólnym i jest przeznaczony dla wektorów lub innych 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))))

Tutaj widzimy, że dla zakresu całkowitego lub wektora mieszanego, contains-elem? działa zgodnie z oczekiwaniami zarówno dla istniejących, jak i nieistniejących elementów w zbiorze. Dla map możemy Szukaj również dowolnej pary klucz-wartość (wyrażonej jako wektor 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] ))))

Łatwo jest również wyszukać zbiór:

  (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)))))
W przypadku map i zestawów łatwiej jest (i bardziej efektywnie) użyć contains-key?, aby znaleźć wpis mapy lub element zestawu:
(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)))

I, dla map, można również wyszukać wartości za pomocą 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)))

Jak widać w teście, każda z tych funkcji działa poprawnie podczas wyszukiwania wartości nil.

 0
Author: Alan Thompson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-17 22:20:53