2010-07-14 14 views
136

¿Cuál es la mejor manera de comprobar si una lista contiene un valor determinado en Clojure?Compruebe si una lista contiene un valor específico en Clojure

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

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

que podría, obviamente, escribir una función sencilla para recorrer la lista y prueba de igualdad, pero seguramente debe haber una manera estándar de hacer esto ?

+7

Extraño de hecho, ¿contiene? tiene que ser la función con el nombre más engañoso en Clojure :) ¿Aquí está la esperanza de que Clojure 1.3 lo cambie de nombre a contains-key? o similar. –

+4

Creo que ahora se habla de muerte varias veces. contiene? no cambiará. Consulte aquí: http://groups.google.com/group/clojure/msg/f2585c149cd0465d y http://groups.google.com/group/clojure/msg/985478420223ecdf – kotarak

+1

@kotarak gracias por el enlace. De hecho estoy de acuerdo con Rich aquí en cuanto al uso del contenido? nombre aunque creo que debería ser alterado para arrojar un error cuando se aplica a una lista o secuencia – mikera

Respuesta

173

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

Hace no comprueba 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 los conjuntos (que se puede considerar que no distinguen entre claves y valores), mapas (así (contains? {:foo 1} :foo) es true) y vectores (pero tenga en cuenta que (contains? [:foo :bar] 0) es true, porque las claves aquí son índices y el vector en cuestión no "contener" el índice 0!).

Para aumentar la confusión, en los casos en que no tiene sentido llamar al contains?, simplemente devuelva 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 al entregar un objeto de un tipo que no admite la prueba de "membresía de clave" prevista.

La forma correcta de hacer lo que estamos tratando de hacer es la siguiente:

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

Durante la búsqueda de uno de un grupo de artículos, se puede utilizar un conjunto más amplio; al buscar false/nil, puede usar false?/nil? - porque (#{x} x) devuelve , por lo tanto (#{nil} nil) es nil; en la búsqueda de uno de varios elementos, algunos de los cuales pueden ser false o nil, puede utilizar

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

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

+0

Gracias Michal - ¡eres una fuente de sabiduría Clojure como siempre! Parece que voy a escribir mi propia función en este caso ... me sorprende un poco que ya no haya uno en el lenguaje central. – mikera

+4

Como dijo Michal: ya hay una función en el núcleo que hace lo que deseas: algo. – kotarak

+2

Arriba, Michal comentó acerca de '(some # {101} '(100 101 102))' diciendo que "la mayoría de las veces esto funciona". ¿No es justo decir que siempre funciona? Estoy usando Clojure 1.4 y la documentación usa este tipo de ejemplo. Funciona para mi y tiene sentido. ¿Hay algún tipo de caso especial en el que no funciona? –

5

Aquí hay una rápida la función de mis utilidades estándar que utilizo 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)))) 
+0

Gracias Greg! Acabo de escribir algo vagamente parecido, lo publiqué arriba ... – mikera

+0

Sí, el suyo tiene la ventaja de que se detendrá tan pronto como encuentre una coincidencia en lugar de seguir mapeando toda la secuencia. – Greg

7

por lo que vale la pena, es mi sencilla implementación de una función para contiene listas:

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

¿Podemos pedir la parte predicada como argumento? Para obtener algo como: '(lista defn-contiene? [Valor predlcl] (let [s (seq coll)] (si s (si (valor pred (first s)) true (recur (rest s) valor)) false))) ' –

111

Aquí es mi norma util para el mismo propósito:

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

¡Gracias por agregar a la colección! Parece que todos han encontrado la necesidad de uno de estos :-) – mikera

+0

Parece un pasatiempo popular, de hecho. ¿Tal vez terminaremos con una colección de todas las formas posibles de implementarlo? :) –

+31

Esta es la solución más simple y segura, ya que también maneja los valores falsy como 'nil' y' false'. Ahora, ¿por qué esto no es parte de clojure/core? –

4

He construido sobre j-g-Fausto version de "lista contiene?". 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)))) 
12

sé que soy un poco más tarde, pero ¿qué pasa:

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

Por fin en clojure 1.4 salidas cierto :)

+2

'(set '(101 102 103))' es lo mismo que '% {101 102 103}'. Entonces su respuesta puede escribirse como '(contains? # {101 102 103} 102)'. –

+3

Esto tiene la desventaja de requerir la conversión de la lista original ''(101 102 103)' a un conjunto. –

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

obras, pero a continuación es mejor:

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

¡Personalmente considero que esta solución es la más elegante en la lista de respuestas! :) –

1

La recomendación ded es utilizar some con un conjunto; consulte la documentación para clojure.core/some.

Podrías usar some dentro de un verdadero predicado verdadero/falso, p. Ej.

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

¿por qué 'if'' true' y 'false'? 'some' ya devuelve valores true-ish y false-ish. – subsub

+0

¿qué tal (algunos # {nil} [nil])? Devolverá nil que se convertirá en falso. –

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

Si usted tiene un vector o lista y desea comprobar si un valorestá contenida en el mismo, se encuentra que contains? no funciona. Michał ya tiene explained why.

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

Hay cuatro cosas que puede probar en este caso:

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

    (contains? #{:a :b :c} :b) ; = true 
    
  2. Uso some, envolviendo el objetivo en un conjunto, de la siguiente manera:

    (some #{:b} [:a :b :c]) ; = :b, which is truthy 
    
  3. El conjunto de métodos abreviados-como-función no funcionará si usted está en busca de un valor Falsy (false o nil).

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

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

    (some false? [true false true]) ; = true 
    
  4. Si necesita hacer este tipo de búsqueda mucho , escribir una función para la que:

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

También, vea Michał’s answer para ver formas de verificar si alguno de objetivos múltiples están contenidos en una secuencia.

5

Aquí está la solución de Lisp clásica:

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

Bien, la razón por la cual es una solución pobre en Clojure es que recualifica la pila en un procesador. Una mejor solución Clojure es

 (defn member? [elt col] (some #(= elt %) col)) 
Esto se debe a que 'some' es potencialmente paralelo a través de los núcleos disponibles. –

2

Es tan simple como usar una serie - similares a los mapas, sólo puede soltarlo en la posición de función. Se evalúa al valor si en el conjunto (que es Truthy) o nil (que es Falsey-):

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

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

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

siempre se puede llamar a métodos Java con la sintaxis .methodName.

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

en mi humilde opinión esta es la mejor respuesta. Qué mal clojure contiene? es tan confusamente nombrado. – mikkom

0

Desde Clojure está construida en Java, que sólo puede llamar a la misma facilidad con la función de Java .indexOf. 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 decir simplemente:

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

El problema con la solución 'recomendada' es que es descansos cuando el valor que está buscando es 'nil'. 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)) 
1
(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 (que [1 2 3] 3?) O (que # {1 2 3} 4 5 3?)

+0

todavía no hay función de núcleo de idioma para ello? – matanster

0

Hay funciones convenientes para este propósito in the Tupelo library. En particular, las funciones contains-elem?, contains-key? y contains-val? son muy útiles. La documentación completa está presente in the API docs.

contains-elem? es el más genérico y está destinado a vectores o cualquier otro 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)))) 

Aquí vemos que para un rango de números enteros o un vector mixta, contains-elem? funciona como se espera para los dos existentes y la no elementos existentes en la colección. Para los mapas, también podemos buscar cualquier par clave-valor (expresado 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 fácil de 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 & conjuntos , es más simple (& más eficiente) a utilizar contains-key? para encontrar una entrada de mapa o un elemento de conjunto:

(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 Valu í 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 cuando funciona correctamente para la búsqueda de nil valores.

Cuestiones relacionadas