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



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



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



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


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

774   17  

17 ответов:

а, 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) и 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:)

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

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

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

вы всегда можете вызвать методы 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

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

  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
    

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

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

(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, вы можете так же легко вызвать .indexOf Java функции. Эта функция возвращает индекс элемента в коллекции, и если он не может найти этот элемент, возвращает -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? является наиболее общим и предназначен для векторов или любой другой 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" ]]
      (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

    Ничего не найдено.