183 votes

Tester si une liste contient une valeur spécifique en Clojure

Quel est le meilleur moyen de tester si une liste contient une valeur donnée en Clojure ?

En particulier, le comportement de contains? me perturbe 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 exister un moyen standard de le faire ?

7 votes

Il est vrai que la fonction contains? est étrange dans Clojure :) On espère que Clojure 1.3 la renommera en contains-key? ou quelque chose de similaire.

4 votes

Je pense que cela a été discuté à plusieurs reprises maintenant. Le contient? ne changera pas. Voir ici: groups.google.com/group/clojure/msg/f2585c149cd0465d et groups.google.com/group/clojure/msg/985478420223ecdf

1 votes

@kotarak merci pour le lien! Je suis en fait d'accord avec Rich ici en ce qui concerne l'utilisation du nom contains? bien que je pense qu'il devrait être modifié pour générer une erreur lorsqu'il est appliqué à une liste ou une séquence

228voto

Michał Marczyk Points 54179

Ah, contains?... supposément l'une des cinq premières questions fréquemment posées concernant Clojure.

Cela ne vérifie pas si une collection contient une valeur; cela vérifie si un élément pourrait être récupéré avec get ou, en d'autres termes, si une collection contient une clé. Cela a du sens pour les ensembles (qui peuvent être considérés comme ne faisant aucune distinction entre clés et valeurs), les maps (donc (contains? {:foo 1} :foo) est true) et les vecteurs (mais notez que (contains? [:foo :bar] 0) est true, car les clés ici sont des indices 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 renvoie 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? lève une erreur lorsqu'il reçoit un objet d'un type qui ne supporte pas le test de "membership" de la clé prévu.

La manière correcte de faire ce que vous essayez de faire est la suivante:

; la plupart du temps cela fonctionne
(some #{101} '(100 101 102))

Lorsque vous cherchez l'un des éléments d'un ensemble, vous pouvez utiliser un ensemble plus grand; lorsque vous cherchez false / nil, vous pouvez utiliser false? / nil? -- car (#{x} x) renvoie x, donc (#{nil} nil) est nil; lorsque vous cherchez l'un des éléments multiples dont certains peuvent être false ou nil, vous pouvez utiliser

(some (zipmap [...les éléments...] (repeat true)) la-collection)

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

0 votes

Merci Michal - tu es une source de sagesse en Clojure comme toujours! Il semble que je vais écrire ma propre fonction dans ce cas... cela me surprend un peu qu'il n'y en ait pas déjà une dans le langage de base.

4 votes

Comme l'a dit Michal - il y a déjà une fonction dans le noyau qui fait ce que vous désirez : some.

2 votes

Ci-dessus, Michal a commenté à propos de (some #{101} '(100 101 102)) en disant que "la plupart du temps ça fonctionne". N'est-il pas juste de dire que cela fonctionne toujours? J'utilise Clojure 1.4 et la documentation utilise ce genre d'exemple. Ça fonctionne pour moi et ça a du sens. Y a-t-il un cas spécial où cela ne fonctionne pas?

149voto

j-g-faustus Points 4315

Voici mon util standard pour le même but :

(defn in? 
  "true si coll contient elm"
  [coll elm]  
  (some #(= elm %) coll))

39 votes

Ceci est la solution la plus simple et la plus sûre, car elle gère également les valeurs fausses comme nil et false. Maintenant, pourquoi cela ne fait-il pas partie de clojure/core?

2 votes

seq pourrait éventuellement être renommé en coll, pour éviter la confusion avec la fonction seq ?

3 votes

@nha Vous pouvez le faire, oui. Cela n'a pas d'importance ici : Puisque nous n'utilisons pas la fonction seq à l'intérieur du corps, il n'y a pas de conflit avec le paramètre du même nom. Mais n'hésitez pas à modifier la réponse si vous pensez que le changement de nom la rendrait plus facile à comprendre.

18voto

Giuliani Sanches Points 363

Je sais que je suis un peu en retard, mais que pensez-vous de :

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

Enfin, dans clojure 1.4, cela renvoie true :)

3 votes

(ensemble '(101 102 103)) est la même que %{101 102 103}. Donc, votre réponse peut être écrite comme (contient? #{101 102 103} 102).

6 votes

Cela présente l'inconvénient de nécessiter la conversion de la liste originale '(101 102 103) en un ensemble.

15voto

jamesqiu Points 636
(non= -1 (.indexOf '(101 102 103) 102)) 

Fonctionne, mais ci-dessous est mieux:

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

0 votes

Certaines retournent nil si aucune correspondance n'est trouvée, pas false

7voto

mikera Points 63056

Pour ce que cela vaut, voici ma simple implémentation d'une fonction contains pour les listes :

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

0 votes

Pouvons-nous demander la partie prédicat en tant qu'argument ? Pour obtenir quelque chose de similaire : (defn list-contains? [pred coll value] (let [s (seq coll)] (if s (if (pred (first s) value) true (recur (rest s) value)) false)))

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X