data-structures clojure

data structures - Pruebe si una lista contiene un valor específico en Clojure



data-structures (17)

¿Cuál es la mejor manera de probar si una lista contiene un valor dado en Clojure?

En particular, ¿el comportamiento de contains? actualmente me está confundiendo:

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

Obviamente, podría escribir una función simple para recorrer la lista y probar la igualdad, pero seguramente debe haber una forma estándar de hacerlo.


Ah, contains? ... supuestamente una de las cinco preguntas frecuentes más frecuentes: Clojure.

No verifica si una colección contiene un valor; comprueba si un elemento se puede recuperar con get o, en otras palabras, si una colección contiene una clave. Esto tiene sentido para conjuntos (que se puede pensar que no hacen distinción entre claves y valores), mapas (entonces (contains? {:foo 1} :foo) es true ) y vectores (pero tenga en cuenta que (contains? [:foo :bar] 0) es true , porque las teclas aquí son índices y el vector en cuestión "contiene" el índice 0 !

¿Para agregar confusión, en los casos en que no tiene sentido llamar contains? , simplemente devuelve false ; esto es lo que sucede en (contains? :foo 1) y también (contains? ''(100 101 102) 101) . Actualización: ¿ En Clojure ≥ 1.5 contains? lanza cuando se le entrega un objeto de un tipo que no admite la prueba de "membresía clave" prevista.

La forma correcta de hacer lo que estás tratando de hacer es la siguiente:

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

Cuando busca uno de un grupo de artículos, puede usar un conjunto más grande; cuando busca false / nil , ¿puede usar false? / nil? - porque (#{x} x) devuelve x , por lo tanto (#{nil} nil) es nil ; cuando busca uno de varios artículos, algunos de los cuales pueden ser false o nil , puede usar

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

(Tenga en cuenta que los elementos se pueden pasar a zipmap en cualquier tipo de colección).


Aquí está la solución clásica de 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)))


Aquí está mi utilidad estándar para el mismo propósito:

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


Aquí hay una función rápida de mis utilidades estándar que uso para este propósito:

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


Como Clojure se basa en Java, puede llamar fácilmente a la función .indexOf Java. Esta función devuelve el índice de cualquier elemento en una colección, y si no puede encontrar este elemento, devuelve -1.

Haciendo uso de esto podríamos simplemente decir:

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


El problema con la solución ''recomendada'' es que se rompe cuando el valor que buscas es ''nulo''. Yo prefiero esta solución:

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


Es tan simple como usar un conjunto: similar a los mapas, puedes soltarlo en la posición de la función. Evalúa el valor si está en el conjunto (que es verdadero) o nil (que es falsey):

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

Si está consultando un vector / lista de tamaño razonable, no tendrá hasta el tiempo de ejecución, también puede usar la función de set :

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


Hay funciones convenientes para este propósito en la biblioteca de Tupelo . En particular, ¿las funciones contains-elem? , contains-key? y contains-val? son muy útiles. La documentación completa está presente en los documentos API .

contains-elem? es el más genérico y está destinado a vectores o cualquier otro elemento de clojure:

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

Aquí vemos que para un rango entero o un vector mixto, contains-elem? funciona según lo esperado para los elementos existentes y no existentes en la colección. Para los mapas, también podemos buscar cualquier pareja clave-valor (expresada como un vector 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] ))))

También es sencillo buscar un conjunto:

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

Para mapas y conjuntos, ¿es más simple (y más eficiente) usar contains-key? para encontrar una entrada de mapa o un elemento establecido:

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

Y, para los mapas, también puede buscar valores con 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)))

Como se ve en la prueba, cada una de estas funciones funciona correctamente cuando se buscan valores nil .


He construido sobre la version de jg-faustus de "list-contains?" Ahora toma cualquier cantidad de argumentos.

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


La forma recomendada es usar some con un conjunto: consulte la documentación de clojure.core/some .

Luego puede usar some dentro de un verdadero predicado verdadero / falso, por ej.

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


Por lo que vale, esta es mi implementación simple de una función contains para listas:

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


Sé que soy un poco más tarde, pero ¿qué hay de:

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

Por fin en clojure 1.4 salidas verdad :)


Si tiene un vector o lista y desea verificar si un valor está contenido en él, ¿encontrará que contains? No funciona. Michał ya ha explicado por qué .

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

Hay cuatro cosas que puedes probar en este caso:

  1. Considere si realmente necesita un vector o lista. Si usa un conjunto en su lugar , contains? trabajará.

    (contains? #{:a :b :c} :b) ; = true

  2. Use some , envolviendo el objetivo en un conjunto, de la siguiente manera:

    (some #{:b} [:a :b :c]) ; = :b, which is truthy

  3. El acceso directo de establecer como función no funcionará si está buscando un valor false ( false o nil ).

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

    En estos casos, debe usar la función de predicado incorporada para ese valor, false? o nil? :

    (some false? [true false true]) ; = true

  4. Si necesita hacer mucho este tipo de búsqueda, escriba una función para ello :

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

Además, vea la respuesta de Michał sobre las formas de verificar si alguno de los múltiples objetivos está contenido en una secuencia.


Siempre puede llamar a los métodos de Java con la sintaxis .methodName.

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


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

ejemplo de uso (¿cuál? [1 2 3] 3) o (¿cuál? # {1 2 3} 4 5 3)


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

funciona, pero a continuación es mejor:

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