Combine transduction output with input into a hashmap

Viewed 149

I want to do the following in Clojure as idiomatically as possible:

  1. transduce a collection
  2. associate each element of the input collection with the corresponding element in the output collection
  3. return the result in a hashmap

Is there a succinct way to do this using core library functions?

If not, what improvements can you suggest to the following implementation?

(defn to-hash [coll xform]
  (reduce
    merge
    (map
      #(apply hash-map %)
      (mapcat hash-map coll (into [] xform coll)))))
2 Answers

something like this should do the trick without intermediate collections:

(defn process [data xform]
  (zipmap data (eduction xform data)))

user> (process [1 2 3] (comp (map inc) (map #(* % %))))
;;=> {1 4, 2 9, 3 16}

the docs on eduction say the following:

Returns a reducible/iterable application of the transducers to the items in coll. Transducers are applied in order as if combined with comp. Note that these applications will be performed every time reduce/iterator is called.

so no additional collection is created.

This is any good, of course, as long as there is one-to-one relationship between input and output elements. What is desired output for (process [1 -2 3] (filter pos?)) or (process [1 1 1 2 2 2] (dedupe)) ?

(by the way, your to-hash implementation has the same flaw)

A transducer is a function that takes a reducing function and returns a new reducing function. To make it work with transducers where there is not a one-to-one mapping from elements in the input collection to the output, you will have to use your transducer to create a new reducing function (step2 in the code below) that will associate elements into your hash map. Something like this.

(def ^:dynamic assoc-k nil)

(defn assoc-step [dst x]
  (assoc dst assoc-k x))

(defn to-hash [coll xform]
  (let [step (xform (completing assoc-step))
        step2 (fn [dst x] (binding [assoc-k x] (step dst x)))]
    (reduce step2 {} coll)))

This implementation is quite basic and I am not sure to which extent it will work with stateful transducers. But it will work with the stateless ones, such as map and filter.

And we can test it with a transducer that keeps odd elements in the input collection and squares them:

(defn square [x] (* x x))

(to-hash (range 10) (comp (filter odd?) (map square)))
;; => {1 1, 3 9, 5 25, 7 49, 9 81}
Related