August 25, 2019
By: Kevin

回答两个常见问题

  1. 怎么在数组里找第一个符合条件的元素?
  2. 怎么快速返回,不用遍历全部元素的那种?
  3. 这两个问题有什么联系?

怎么在数组里找第一个符合条件的元素?

答案:使用some函数

一开始学习Clojure 的小伙伴可能会用filter来做,filter的问题是需要遍历全部元素。虽然确实拿到了结果,但是性能会低一些。

(first (filter #(= % :a) [:a :b :c :d]))

用some来做

(some #{:a} [:a :b :c :d])

因为set也是个函数

(#{:a} :a)
(#{:a} :b)

怎么快速返回,不用遍历全部元素的那种?

答案:在reduce中使用reduced 函数

让我们先回顾一下reduce函数。reduce 虽然经常和map, filter一起被提及,实质上reduce要更底层一些,我们可以用reduce来实现mapfilter

可以说reduce就是clojure默认的遍历序列和数组的方式。大多数情况下我们reduce会遍历每一个元素。但是,如果我们需要early return(或者说short circuit), reduce也为我们提供了机制。 以求和为例子:

(reduce (fn [a v] (+ a v)) (range 10))

如果我们想一旦求和的结果大于100,我们就返回:big,并且中断计算。

(reduce (fn [a v] (if (< a 100) (+ a v) (reduced :big))) (range 10))
(reduce (fn [a v] (if (< a 100) (+ a v) (reduced :big))) (range 20))

这个机制下,我们甚至可以处理无限序列range

(reduce (fn [a v] (if (< a 100) (+ a v) (reduced :big))) (range))

这两个问题有什么联系?

答案: 都是short-curcuits, 提前返回,无须遍历全部元素

看一下some的源代码:

(defn some
  "Returns the first logical true value of (pred x) for any x in coll,
  else nil.  One common idiom is to use a set as pred, for example
  this will return :fred if :fred is in the sequence, otherwise nil:
  (some #{:fred} coll)"
  {:added "1.0"
   :static true}
  [pred coll]
    (when-let [s (seq coll)]
      (or (pred (first s)) (recur pred (next s)))))

or实现的呀,我们都知道orand这种逻辑运算符天生就带短路功能, 那逻辑运为什么可以实现短路呢? Macro!

先到此位置吧.

最后我们用reduced实现一个自己版本的some吧:

(defn resome [pred koll]
  (reduce (fn [_ c] (when-let [x (pred c)] (reduced x)))
  nil koll))
;;自己测试下吧:
;;(resome #{4} [3 4 2 3 2])

;;(resome even? [3 41 25 3 2])

;;(resome even? [3 41 25 3 27])

明白了吧?

Tags: clojure