在Clojure中一次执行多次减少

时间:2016-03-28 20:02:06

标签: clojure functional-programming reduce

在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库中有什么能够满足我的需求吗?如果没有,我的功能在哪里出错?

3 个答案:

答案 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)]))

现在我们可以定义您想要的功能,它将juxtreduce结合为......

(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