在Clojure中,我希望找到多次减少的结果,同时只消耗序列一次。在Java中,我会做类似以下的事情:
double min = Double.MIN_VALUE;
double max = Double.MAX_VALUE;
for (Item item : items) {
double price = item.getPrice();
if (price > min) {
min = price;
}
if (price < max) {
max = price;
}
}
在Clojure中,我可以通过使用循环和重复来做同样的事情,但它不是很容易组合 - 我想做一些让你根据需要添加其他聚合函数的东西。
我已经写了以下功能来执行此操作:
(defn reduce-multi
"Given a sequence of fns and a coll, returns a vector of the result of each fn
when reduced over the coll."
[fns coll]
(let [n (count fns)
r (rest coll)
initial-v (transient (into [] (repeat n (first coll))))
fns (into [] fns)
reduction-fn
(fn [v x]
(loop [v-current v, i 0]
(let [y (nth v-current i)
f (nth fns i)
v-new (assoc! v-current i (f y x))]
(if (= i (- n 1))
v-new
(recur v-new (inc i))))))]
(persistent! (reduce reduction-fn initial-v r))))
可以通过以下方式使用:
(reduce-multi [max min] [4 3 6 7 0 1 8 2 5 9])
=> [9 0]
我很欣赏它没有以最惯用的方式实现,但主要的问题是它的速度大约是一次减速的10倍。这可能对于大量执行大量减少的情况很有用,其中seq正在执行大量IO,但肯定会更好。
现有的Clojure库中有什么能够满足我的需求吗?如果没有,我的功能在哪里出错?
答案 0 :(得分:3)
这就是我要做的事情:只需将此任务委托给核心reduce
函数,如下所示:
(defn multi-reduce
([fs accs xs] (reduce (fn [accs x] (doall (map #(%1 %2 x) fs accs)))
accs xs))
([fs xs] (when (seq xs)
(multi-reduce fs (repeat (count fs) (first xs))
(rest xs)))))
在repl中:
user> (multi-reduce [+ * min max] (range 1 10))
(45 362880 1 9)
user> (multi-reduce [+ * min max] [10])
(10 10 10 10)
user> (multi-reduce [+ * min max] [])
nil
user> (multi-reduce [+ * min max] [1 1 1000 0] [])
[1 1 1000 0]
user> (multi-reduce [+ * min max] [1 1 1000 0] [1])
(2 1 1 1)
user> (multi-reduce [+ * min max] [1 1 1000 0] (range 1 10))
(46 362880 1 9)
user> (multi-reduce [max min] (range 1000000))
(999999 0)
答案 1 :(得分:1)
可缩小集合的reduce
代码很快。因此,值得尝试将multi-reduce
基于核心reduce
。为此,我们必须能够构建正确形状的缩减函数。这样做的辅助功能是......
(defn juxt-reducer [f g]
(fn [[fa ga] x] [(f fa x) (g ga x)]))
现在我们可以定义您想要的功能,它将juxt
与reduce
结合为......
(defn juxt-reduce
([[f g] coll]
(if-let [[x & xs] (seq coll)]
(juxt-reduce (list f g) [x x] xs)
[(f) (g)]))
([[f g] init coll]
(reduce (juxt-reducer f g) init coll)))
例如,
(juxt-reduce [max min] [4 3 6 7 0 1 8 2 5 9]) ;=> [9 0]
以上是核心reduce
的形状。它可以明确地扩展到应对两个以上的功能。而且我希望它可以比你的可缩减收藏更快。
答案 2 :(得分:-1)
我将如何做到这一点:
(ns clj.core
(:require [clojure.string :as str] )
(:use tupelo.core))
(def data (flatten [ (range 5 10) (range 5) ] ))
(spyx data)
(def result (reduce (fn [cum-result curr-val] ; reducing (accumulator) fn
(it-> cum-result
(update it :min-val min curr-val)
(update it :max-val max curr-val)))
{ :min-val (first data) :max-val (first data) } ; inital value
data)) ; seq to reduce
(spyx result)
(defn -main [] )
;=> data => (5 6 7 8 9 0 1 2 3 4)
;=> result => {:min-val 0, :max-val 9}
因此,缩减函数(fn ...)通过序列的每个元素携带类似{:min-val xxx :max-val yyy}
的映射,更新min&amp;每步所需的最大值。
虽然这确实只有一次通过数据,但每个元素调用update
两次需要做很多额外的工作。除非您的序列非常不寻常,否则通过数据进行两次(非常有效的)传递可能更有效:
(def min-val (apply min data))
(def max-val (apply max data))
(spyx min-val)
(spyx max-val)
;=> min-val => 0
;=> max-val => 9