web-dev-qa-db-fra.com

Teste si une liste contient une valeur spécifique dans Clojure

Quel est le meilleur moyen de vérifier si une liste contient une valeur donnée dans Clojure?

En particulier, le comportement de contains? m'embrouille actuellement:

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

Je pourrais évidemment écrire une fonction simple pour parcourir la liste et tester l’égalité, mais il doit sûrement y avoir un moyen standard de le faire?

150
mikera

Ah, contains?... soi-disant l’une des cinq questions les plus fréquemment posées concernant Clojure.

Il ne vérifie pas si une collection contient une valeur; il vérifie si un élément peut être récupéré avec get ou, en d'autres termes, si une collection contient une clé. Cela a du sens pour les ensembles (ce qui peut être considéré comme ne faisant aucune distinction entre les clés et les valeurs), les cartes (donc (contains? {:foo 1} :foo) est true) et des vecteurs (mais notez que (contains? [:foo :bar] 0) est true, car les clés ici sont des index et le vecteur en question "contient" l’index 0!).

Pour ajouter à la confusion, dans les cas où il n’a pas de sens d’appeler contains?, il retourne simplement false; c'est ce qui se passe dans (contains? :foo 1) et aussi (contains? '(100 101 102) 101).  Mise à jour: Dans Clojure ≥ 1.5 contains? lance un objet d'un type qui ne prend pas en charge le test "d'appartenance à une clé" prévu.

La bonne façon de faire ce que vous essayez de faire est la suivante:

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

Lorsque vous recherchez l'un des objets, vous pouvez utiliser un plus grand ensemble. lors de la recherche de false/nil, vous pouvez utiliser false?/nil? -- car (#{x} x) retourne x, donc (#{nil} nil) est nil; lors de la recherche d’un ou plusieurs éléments dont certains peuvent être false ou nil, vous pouvez utiliser

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

(Notez que les éléments peuvent être passés à zipmap dans n'importe quel type de collection.)

195
Michał Marczyk

Voici mon util standard pour le même but:

(defn in? 
  "true if coll contains Elm"
  [coll Elm]  
  (some #(= Elm %) coll))
121
j-g-faustus

Je sais que je suis un peu en retard, mais qu'en est-il:

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

Enfin, dans la version 1.4, les sorties sont vraies :)

14
Giuliani Deon

Vous pouvez toujours appeler les méthodes Java avec la syntaxe .methodName.

(.contains [100 101 102] 101) => true
12
Yury Litvinov
(not= -1 (.indexOf '(101 102 103) 102))

Fonctionne, mais en dessous c'est mieux:

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

Pour ce qu’il vaut, c’est ma simple implémentation d’une fonction contient pour les listes:

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

Si vous avez un vecteur ou une liste et que vous voulez vérifier si valeur est contenu dans celui-ci, vous constaterez que contains? ne fonctionne pas. Michał a déjà expliqué pourquoi .

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

Dans ce cas, vous pouvez essayer quatre choses:

  1. Déterminez si vous avez vraiment besoin d’un vecteur ou d’une liste. Si vous utilisez un ensemble à la place , contains? marchera.

    (contains? #{:a :b :c} :b) ; = true
    
  2. Utilisez some, en enveloppant la cible dans un ensemble, comme suit:

    (some #{:b} [:a :b :c]) ; = :b, which is truthy
    
  3. Le raccourci set-as-function ne fonctionnera pas si vous recherchez une valeur de fausseté (false ou nil).

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

    Dans ces cas, vous devriez utiliser la fonction de prédicat intégrée pour cette valeur, false? ou nil? :

    (some false? [true false true]) ; = true
    
  4. Si vous devez beaucoup faire ce genre de recherche, écrivez une fonction pour :

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

Voir aussi réponse de Michał pour savoir comment vérifier si des cibles multiples sont contenues dans une séquence.

6
Rory O'Kane

Voici la solution LISP classique:

(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
Simon Brooke

Voici une rapide fonction de mes utilitaires standard que j'utilise à cette fin:

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

J'ai construit sur j-g-faustus version de "list-contient?". Il faut maintenant un nombre quelconque d'arguments.

(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))))
4
Urs Reupke

C'est aussi simple que d'utiliser un ensemble - semblable aux cartes, vous pouvez simplement le déposer à la position de la fonction. Il évalue à la valeur si dans l'ensemble (ce qui est la vérité) ou nil (ce qui est falsey):

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

Si vous vérifiez par rapport à un vecteur/une liste de taille raisonnable que vous n’aurez qu’à l’exécution, vous pouvez également utiliser la fonction set:

; (def nums '(100 101 102))
((set nums) 101) ; 101
2
Brad Koch
(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 )))

exemple d'utilisation (lequel? [1 2 3] 3) ou (quel? # {1 2 3} 4 5 3)

1
Michael
(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))))))
1
David

La méthode recommandée consiste à utiliser some avec un ensemble - voir la documentation de clojure.core/some.

Vous pouvez ensuite utiliser some dans un vrai prédicat vrai/faux, par exemple.

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

Il existe des fonctions pratiques à cet effet dans la bibliothèque Tupelo . En particulier, les fonctions contains-elem?, contains-key?, et contains-val? sont très utiles. Une documentation complète est présente dans la documentation de l'API .

contains-elem? est le plus générique et est destiné aux vecteurs ou à tout autre 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))))

Nous voyons ici que pour une plage entière ou un vecteur mixte, contains-elem? fonctionne comme prévu pour les éléments existants et non existants de la collection. Pour les cartes, nous pouvons également rechercher n’importe quelle paire clé-valeur (exprimée sous la forme d’un vecteur 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] ))))

Il est également simple de rechercher un ensemble:

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

Pour les cartes et les ensembles, il est plus simple (et plus efficace) d’utiliser contains-key? pour trouver une entrée de carte ou un élément de jeu:

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

Et, pour les cartes, vous pouvez également rechercher des valeurs avec 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)))

Comme indiqué dans le test, chacune de ces fonctions fonctionne correctement lors de la recherche de valeurs nil.

0
Alan Thompson

Le problème avec la solution "recommandée" est qu’il s’agit d’une rupture lorsque la valeur recherchée est "nulle". Je préfère cette solution:

(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))
0
Simon Brooke

Clojure étant basé sur Java, vous pouvez tout aussi facilement appeler le .indexOf Java. Cette fonction renvoie l’index de tout élément d’une collection et, si elle ne trouve pas cet élément, renvoie -1.

En utilisant cela, nous pourrions simplement dire:

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