web-dev-qa-db-fra.com

Retourne le premier élément d'une carte / liste / séquence qui satisfait un prédicat

Je recherche une fonction qui renvoie le premier élément d'une séquence pour laquelle un fn est évalué à vrai. Par exemple:

(first-map (fn [x] (= x 1)) '(3 4 1))

La fausse fonction ci-dessus doit renvoyer 1 (le dernier élément de la liste). Y a-t-il quelque chose comme ça à Clojure?

49
Matthew
user=> (defn find-first
         [f coll]
         (first (filter f coll)))
#'user/find-first
user=> (find-first #(= % 1) [3 4 1])
1

Edit: Une concurrence. :) Non. Il ne s'applique pas f à toute la liste. Uniquement aux éléments jusqu'au premier correspondant en raison de la paresse de filter.

60
kotarak

Dans votre cas, l'idiome est

(some #{1} [1 2 3 4])

Comment ça marche: # {1} est un ensemble littéral. Un ensemble est également une fonction évaluant son argument si l'argument est présent dans l'ensemble et nul sinon. Tout élément d'ensemble est une valeur "véridique" (enfin, à l'exception d'un faux booléen, mais c'est une rareté dans un ensemble). some renvoie la valeur de retour du prédicat évalué par rapport au premier membre de collection pour lequel le résultat était véridique.

48
Marko Topolnik

J'ai essayé plusieurs méthodes mentionnées dans ce fil (JDK 8 et Clojure 1.7), et j'ai fait quelques tests de référence:

repl> (defn find-first
         [f coll]
         (first (filter f coll)))
#'cenx.parker.strategies.vzw.repl/find-first

repl> (time (find-first #(= % 50000000) (range)))
"Elapsed time: 5799.41122 msecs"
50000000

repl> (time (some #{50000000} (range)))
"Elapsed time: 4386.256124 msecs"
50000000

repl> (time (reduce #(when (= %2 50000000) (reduced %2)) nil (range)))
"Elapsed time: 993.267553 msecs"
50000000

Les résultats montrent que reduce way peut être la solution la plus efficace comme dans clojure 1.7.

14
象嘉道

Je pense que some est le meilleur outil pour le travail:

(some #(if (= % 1) %) '(3 4 1))
12
vemv

En utilisant drop-while au lieu de filter devrait traiter la "sur-application" de f pour les séquences fragmentées:

(defn find-first [f coll]
  (first (drop-while (complement f) coll)))
;;=> #'user/find-first

(find-first #(= % 1) [3 4 1])
;;=> 1
3
Dimagog

En 2016, un patch a été soumis au noyau clojure qui a ajouté un raccourci efficace pour (first (filter pred coll)) idiome, il s'appelait seek.

L'implémentation a évité des problèmes inhérents à la fois avec (first (filter)) et (some #(when (pred))) alternatives. Autrement dit, il fonctionne efficacement avec des séquences en morceaux et joue Nice avec nil? et false? prédicats.

Pièce:

(defn seek
  "Returns first item from coll for which (pred item) returns true.
   Returns nil if no such item is present, or the not-found value if supplied."
  {:added  "1.9" ; note, this was never accepted into clojure core
   :static true}
  ([pred coll] (seek pred coll nil))
  ([pred coll not-found]
   (reduce (fn [_ x]
             (if (pred x)
               (reduced x)
               not-found))
           not-found coll)))

Exemples:

(seek odd? (range)) => 1
(seek pos? [-1 1]) => 1
(seek pos? [-1 -2] ::not-found) => ::not-found
(seek nil? [1 2 nil 3] ::not-found) => nil

Finalement, le patch a été rejeté:

Après examen, nous avons décidé de ne pas l'inclure. L'utilisation de la recherche linéaire (et en particulier de la recherche linéaire imbriquée) entraîne de mauvaises performances - il est souvent préférable d'utiliser d'autres types de structures de données et c'est pourquoi cette fonctionnalité n'a pas été incluse dans le passé. ~ Alex Miller 12/May/17 3:34 PM

2
Casey