返回满足谓词的map/list/sequence中的第一项

Mat*_*hew 49 clojure

我正在寻找一个函数,它返回序列中的第一个元素,其中fn的计算结果为true.例如:

(first-map (fn [x] (= x 1)) '(3 4 1))
Run Code Online (Sandbox Code Playgroud)

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

kot*_*rak 62

user=> (defn find-first
         [f coll]
         (first (filter f coll)))
#'user/find-first
user=> (find-first #(= % 1) [3 4 1])
1
Run Code Online (Sandbox Code Playgroud)

编辑:并发.:)不.它不适f用于整个列表.由于懒惰,仅限于第一个匹配的元素filter.

  • 谢谢@kotarak,我担心这将处理集合中的所有元素,这在大型列表中是不可取的.也许我需要创建一个重复的函数,直到它找到满足条件的元素. (4认同)
  • @Matthew modulo chunked sequence.根据块大小,可以将`f`应用于更多元素. (4认同)
  • 我看到你的编辑。太棒了!非常感谢。 (2认同)

Mar*_*nik 49

在你的情况下,成语是

(some #{1} [1 2 3 4])
Run Code Online (Sandbox Code Playgroud)

工作原理:#{1}是一个集合文字.如果arg存在于集合中,则set也是评估其arg的函数,否则为nil.任何set元素都是一个"truthy"值(好吧,除了布尔值false,但这在集合中很少见).some返回对结果为真的第一个集合成员计算的谓词的返回值.


象嘉道*_*象嘉道 14

我尝试了这个线程中提到的几个方法(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
Run Code Online (Sandbox Code Playgroud)

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

  • 很有意思。感谢您测试这些,xando。如果有来自 [Criterium](https://github.com/hugoduncan/criterium) 的数字会更好,但我的猜测是,通过搜索序列中到目前为止的项目,JVM 有机会优化代码。 (2认同)

vem*_*emv 12

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

(some #(if (= % 1) %) '(3 4 1))
Run Code Online (Sandbox Code Playgroud)

  • 这不会受到分块序列效应的影响。但在 `1` 是 `nil` 或 `false` 的情况下不起作用。天啊。 (4认同)
  • 认为`f`是`#(包含?#{0"false"false}%)`.他说"评估fn为真". (3认同)

Cas*_*sey 12

2016 年有一个补丁提交给 clojure 核心(first (filter pred coll)),它为习语添加了一个有效的快捷方式,它被称为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)))
Run Code Online (Sandbox Code Playgroud)

例子:

(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
Run Code Online (Sandbox Code Playgroud)

最终补丁被拒绝了:

经过审查,我们决定不希望包含此内容。使用线性搜索(尤其是嵌套线性搜索)会导致性能不佳 - 通常最好使用其他类型的数据结构,这就是过去未包含此功能的原因。~亚历克斯·米勒 17 年 5 月 12 日下午 3:34


Dim*_*gog 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
Run Code Online (Sandbox Code Playgroud)