Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perform multiple reductions in a single pass in Clojure

In Clojure I want to find the result of multiple reductions while only consuming the sequence once. In Java I would do something like the following:

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;
    }
}

In Clojure I could do much the same thing by using loop and recur, but it's not very composable - I'd like to do something that lets you add in other aggregation functions as needed.

I've written the following function to do this:

(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))))

This can be used in the following way:

(reduce-multi [max min] [4 3 6 7 0 1 8 2 5 9])
=> [9 0]

I appreciate that it's not implemented in the most idiomatic way, but the main problem is that it's about 10x as slow as doing the reductions one at at time. This might be useful for lots performing lots of reductions where the seq is doing heavy IO, but surely this could be better.

Is there something in an existing Clojure library that would do what I want? If not, where am I going wrong in my function?

like image 719
Robert Johnson Avatar asked Oct 19 '22 12:10

Robert Johnson


1 Answers

that's what i would do: simply delegate this task to a core reduce function, like this:

(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)))))

in 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)
like image 81
leetwinski Avatar answered Nov 02 '22 23:11

leetwinski