Michael Barton
Michael Barton

Reputation: 9546

Alternatives to repeated use of partial when using clojure's `comp`

Given a collection"

[{:key "key_1" :value "value_1"}, {:key "key_2" :value "value_2"}]

I would like to convert this to:

{"key_1" "value_1" "key_2" "value_2"}

An function to do this would be:

(defn long->wide [xs]
    (apply hash-map (flatten (map vals xs))))

I might simplify this using the threading macro:

(defn long->wide [xs]
  (->> xs
       (map vals)
       (flatten)
       (apply hash-map)))

This still requires explicit definition of the function argument which I am not doing anything with other than passing to the first function. I might then rewrite this using comp to remove this:

(def long->wide
  (comp (partial apply hash-map) flatten (partial map vals)))

This however requires repeated use of partial which to me is a lot of noise in the function.

Is there a some function in clojure that combines comp and ->> so I can create a higher order function without repeated use of partial, and also which out having to create a new function?

Upvotes: 2

Views: 238

Answers (3)

cfrick
cfrick

Reputation: 37008

Since many of the answers here already don't answer the original question, but suggest different approaches, I put that one back up too.

I'd go with reduce and destructuring:

(reduce 
  (fn [m {:keys [key value]}] 
    (assoc m key value)) 
  {} 
  [{:key "key_1" :value "value_1"}, {:key "key_2" :value "value_2"}])

Note, that this will also work with string keys (which you mentioned in the comments) (note :strs):

    (reduce 
      (fn [m {:strs [key value]}] 
        (assoc m key value)) 
      {} 
      [{"key" "key_1" "value" "value_1"}, {"key" "key_2" "value" "value_2"}])

Another (point-free) version, when using keywords:

(partial (into {} (map (juxt :key :value))))

Since you mentioned in the comments, that you are using values from a DB, there might also be the chance, that you can switch to just return value tuples. Then the whole operation is just:

(into {} [["key_1" "value_1"]["key_2" "value_2"]])  

Also note, that the use of vals on a map and expecting "insertion order" is dangerous. Small maps are ordered only by accident:

user=> (take 3 (zipmap (range 3) (range 3)))
([0 0] [1 1] [2 2])
user=> (take 3 (zipmap (range 100) (range 100)))
([0 0] [65 65] [70 70])

Upvotes: 3

Mike
Mike

Reputation: 165

As with clojure, so many ways to solve most problems.

(partial #(reduce (fn [r m] (assoc r (m :key) (m :value)))
                  {}
                  %)))

Not sure if the creation of anonymous functions violates your condition or not but this isn't adding functions to the namespace so I thought I'd throw it out there. This also has the benefit of not requiring the keys in the input maps to be keywords as :key and :value can be replaced with values of any type since the map is in the function position. For example:

(partial #(reduce (fn [r m] (assoc r (m "key") (m "value")))
                  {}
                  %)))

Upvotes: 1

user4813927
user4813927

Reputation:

An other alternative to the nice answers is also:

(apply hash-map (mapcat vals [{:key "key_1" :value "value_1"}, {:key "key_2" :value "value_2"}]))

or:

((comp #(apply hash-map %) #(mapcat vals %)) [{:key "key_1" :value "value_1"}, {:key "key_2" :value "value_2"}])

which are exactly the same.

Upvotes: 1

Related Questions