61

我正在寻找一个函数,它返回 fn 评估为 true 的序列中的第一个元素。例如:

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

上面的假函数应该返回 1(列表中的最后一个元素)。Clojure中有这样的东西吗?

4

6 回答 6

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

编辑:并发。:) 不。它不适f用于整个列表。由于filter.

于 2012-04-17T14:00:50.037 回答
56

在你的情况下,成语是

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

它是如何工作的:#{1} 是一个集合文字。集合也是一个函数,如果 arg 存在于集合中,则评估其 arg,否则评估为 nil。任何集合元素都是“真实”值(好吧,除了布尔值 false,但这是集合中的罕见)。some返回针对结果为真的第一个集合成员评估的谓词的返回值。

于 2012-04-17T14:04:57.747 回答
22

我尝试了这个线程中提到的几种方法(JDK 8 和 Clojure 1.7),并做了一些基准测试:

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

结果表明,这种reduce方式可能是 clojure 1.7 中最有效的解决方案。

于 2015-09-04T19:14:23.803 回答
13

2016 年有一个补丁提交给 clojure core(first (filter pred coll)) ,它为idiom添加了一个有效的快捷方式,它被称为seek.

该实现避免了在此使用(first (filter))(some #(when (pred)))替代方案的问题。也就是说,它可以有效地处理分块序列,并且可以很好地处理nil?false?谓词。

修补:

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

例子:

(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

最终补丁被拒绝:

经审查,我们决定不希望将其包括在内。使用线性搜索(尤其是嵌套线性搜索)会导致性能下降——通常最好使用其他类型的数据结构,这就是为什么过去没有包含此功能的原因。~亚历克斯·米勒 12/5/17 下午 3:34

于 2018-12-09T12:15:59.300 回答
12

我认为some是完成这项工作的最佳工具:

(some #(if (= % 1) %) '(3 4 1))
于 2012-04-17T14:04:53.857 回答
7

使用drop-while而不是应该解决分块序列filter的“过度应用” :f

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

(find-first #(= % 1) [3 4 1])
;;=> 1
于 2015-02-10T02:38:09.700 回答