August 25, 2019
By: Kevin
回答两个常见问题
怎么在数组里找第一个符合条件的元素?
答案:使用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来实现map和filter。
可以说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实现的呀,我们都知道or和and这种逻辑运算符天生就带短路功能, 那逻辑运为什么可以实现短路呢? 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])
明白了吧?