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?
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
.
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.
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.
Je pense que some
est le meilleur outil pour le travail:
(some #(if (= % 1) %) '(3 4 1))
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
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