Проверьте, содержит ли список конкретное значение в Clojure - PullRequest
150 голосов
/ 14 июля 2010

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

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

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

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

Ответы [ 17 ]

193 голосов
/ 14 июля 2010

Ах, contains? ... предположительно, один из пяти часто задаваемых вопросов о: 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 в любом типе коллекции.)

121 голосов
/ 14 июля 2010

Вот мой стандартный утилит для той же цели:

(defn in? 
  "true if coll contains elm"
  [coll elm]  
  (some #(= elm %) coll))
14 голосов
/ 19 сентября 2012

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

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

Наконец в clojure 1.4 выводится истина:)

12 голосов
/ 19 августа 2015

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

(.contains [100 101 102] 101) => true
10 голосов
/ 10 октября 2012
(not= -1 (.indexOf '(101 102 103) 102))

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

(some #(= 102 %) '(101 102 103)) 
7 голосов
/ 14 июля 2010

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

(defn list-contains? [coll value]
  (let [s (seq coll)]
    (if s
      (if (= (first s) value) true (recur (rest s) value))
      false)))
6 голосов
/ 24 ноября 2013

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

; 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. Ярлык set-as-function не будет работать, если вы ищете ложное значение (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
    

Также см. ответ Михала о способах проверки того, содержится ли в последовательности какая-либо из кратных целей.

5 голосов
/ 05 апреля 2014

Вот классическое решение 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 голосов
/ 14 июля 2010

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

(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))))
4 голосов
/ 06 октября 2011

Я основывался на j-g-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))))
...