Проверьте, содержит ли список определенное значение в Clojure

Каков наилучший способ проверить, содержит ли список заданное значение в Clojure?

В частности, поведение contains? в настоящее время меня путает:

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

Я мог бы, очевидно, написать простую функцию, чтобы пересечь список и проверить равенство, но обязательно должен быть стандартный способ сделать это?

Ответ 1

Ah, contains?... предположительно один из пяти наиболее часто задаваемых вопросов re: 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) is nil; при поиске одного из нескольких элементов, некоторые из которых могут быть false или nil, вы можете использовать

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

(Обратите внимание, что элементы могут быть переданы в zipmap в любом типе коллекции.)

Ответ 2

Здесь мой стандарт используется для той же цели:

(defn in? 
  "true if coll contains elm"
  [coll elm]  
  (some #(= elm %) coll))

Ответ 3

Я знаю, что я немного опоздал, но как насчет:

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

Наконец-то в clojure 1.4 выводы верные :)

Ответ 4

Вы всегда можете вызывать java-методы с синтаксисом .methodName.

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

Ответ 5

(not= -1 (.indexOf '(101 102 103) 102))

Работает, но ниже лучше:

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

Ответ 6

Для чего это стоит, это моя простая реализация функции contains для списков:

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

Ответ 7

Если у вас есть вектор или список и вы хотите проверить, содержится ли в нем значение, вы обнаружите, что contains? делает не работа. Michał уже объяснил, почему.

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

В этом случае вы можете попробовать четыре вещи:

  • Рассмотрим, действительно ли вам нужен вектор или список. Если вы используете набор, contains? будет работать.

    (contains? #{:a :b :c} :b) ; = true
    
  • Используйте some, обернув цель в набор, следующим образом:

    (some #{:b} [:a :b :c]) ; = :b, which is truthy
    
  • Ярлык set-as-function не будет работать, если вы ищете значение фальшивки (false или nil).

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

    В этих случаях вы должны использовать встроенную функцию предиката для этого значения, false? или nil?:

    (some false? [true false true]) ; = true
    
  • Если вам нужно будет делать такой поиск много, напишите для него функцию:

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

Также см. ответ Michałs, чтобы узнать, содержит ли какая-либо из нескольких целей в последовательности.

Ответ 8

Вот быстрая функция из моих стандартных утилит, которые я использую для этой цели:

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

Ответ 9

Здесь классическое решение 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)))

Ответ 10

Я построил на j-g-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))))

Ответ 11

Это так же просто, как использование набора - похожего на карты, вы можете просто отбросить его в положение функции. Он оценивает значение, если в наборе (который является правдивым) или nil (что ложно):

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

Если вы проверяете вектор или список с достаточным размером, которые не будут доступны до выполнения, вы также можете использовать функцию set:

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

Ответ 12

Рекомендуемый способ - использовать some с набором - см. документацию для clojure.core/some.

Затем вы можете использовать some в реальном истинном/ложном предикате, например.

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

Ответ 13

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

Ответ 14

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

Ответ 15

Поскольку Clojure построен на Java, вы также можете легко вызвать .indexOf Java .indexOf. Эта функция возвращает индекс любого элемента в коллекции, и, если она не может найти этот элемент, возвращает -1.

Воспользовавшись этим, мы могли бы просто сказать:

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

Ответ 16

Проблема с "рекомендуемым" решением заключается в том, что он ломается, когда ценность, которую вы ищете, равна "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))

Ответ 17

Для этой цели существуют удобные функции в библиотеке 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.