Проверьте, содержит ли список определенное значение в Clojure
каков наилучший способ проверить, содержит ли список заданное значение в Clojure?
в частности, поведение contains? в настоящее время сбивает меня с толку:
(contains? '(100 101 102) 101) => false
я мог бы, очевидно, написать простую функцию для прохождения списка и проверки на равенство, но наверняка должен быть стандартный способ сделать это?
17 ответов:
а,
contains?... предположительно, один из пяти лучших часто задаваемых вопросов re: Clojure.это не проверьте, содержит ли коллекция значение; он проверяет, может ли элемент быть получен с помощью
getили, другими словами, содержит ли коллекция ключ. Это имеет смысл для наборов (которые можно рассматривать как не делающие различия между ключами и значениями), карт (так(contains? {:foo 1} :foo)иtrue) и векторов (но обратите внимание, что(contains? [:foo :bar] 0)иtrue, потому что ключи здесь индексы и рассматриваемый вектор "содержат" индекс0!).
чтобы добавить к путанице, в случаях, когда это не имеет смысла называтьобновление: В Clojure ≥ 1.5contains?, Он просто возвращаетfalse; вот что происходит в(contains? :foo 1)и(contains? '(100 101 102) 101).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в любом типе коллекции.)
вот мой стандартный util для той же цели:
(defn in? "true if coll contains elm" [coll elm] (some #(= elm %) coll))
Я знаю, что я немного поздно, но как насчет:
(contains? (set '(101 102 103)) 102)наконец в clojure 1.4 выводит true:)
вы всегда можете вызвать методы java С.синтаксис имени метода.
(.contains [100 101 102] 101) => true
для чего это стоит, это моя простая реализация функции contains для списков:
(defn list-contains? [coll value] (let [s (seq coll)] (if s (if (= (first s) value) true (recur (rest s) value)) false)))
если у вас есть вектор или список и хотите проверить, является ли стоимостью содержится в нем, вы найдете, что
contains?не работает. Михал уже объяснил, почему.; 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кроме того, см. ответ Михала для способов проверить, есть ли какой-либо из несколько цели содержатся в последовательности.
вот быстрая функция из моих стандартных утилит, которые я использую для этой цели:
(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)))
Я построил на 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))))
это так просто, как с помощью набора для, Вы можете просто бросить его в установки функции. Он оценивает значение if в наборе (который является истинным) или
nil(который является falsey):(#{100 101 102} 101) ; 101 (#{100 101 102} 99) ; nilесли вы проверяете относительно разумного размера вектора / списка, который у вас не будет до времени выполнения, вы также можете использовать :
; (def nums '(100 101 102)) ((set nums) 101) ; 101
рекомендуется использовать
someС набором - см. документацию дляclojure.core/some.затем вы могли бы использовать
someв реальном истинном / ложном предикате, например(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 )))пример использования (что? [ 1 2 3 ] 3) или (что? #{ 1 2 3} 4 5 3)
Так как Clojure построен на Java, вы можете так же легко вызвать
.indexOfJava функции. Эта функция возвращает индекс элемента в коллекции, и если он не может найти этот элемент, возвращает -1.используя это, мы могли бы просто сказать:
(not= (.indexOf [1 2 3 4] 3) -1) => true
проблема с "рекомендуемым" решением заключается в том, что это разрывы, когда значение, которое вы ищете, равно "нулю". Я предпочитаю это решение:
(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))
есть удобные функции для этой цели в библиотеке Тьюпело. В частности, функции
contains-elem?,contains-key?иcontains-val?очень полезны. Полная документация присутствует в API docs.
contains-elem?является наиболее общим и предназначен для векторов или любой другой clojureseq:(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" ]] (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 ))) (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" }] (isnt (contains-elem? coll nil )) (isnt (contains-elem? coll [1 :no-way] )) (is (contains-elem? coll [1 :two])) (is (contains-elem? coll ["three" ]))) (let [coll {1 nil "three" }] (isnt (contains-elem? coll [nil 1] )) (is (contains-elem? coll [1 nil] ))) (let [coll {nil 2 "three" }] (isnt (contains-elem? coll [1 nil] )) (is (contains-elem? coll [nil 2] ))))также легко найти набор:
(testing "sets" (let [coll #{1 :two "three" }] (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 ))) (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значения.
Comments