DEV Community

Clarice Bouwer
Clarice Bouwer

Posted on

Select keys to form a tupled map in Clojure

Example:

Taking a vector of maps that looks like

[{:fruit "apple" :color "red" :brand "Granny Smith"}
 {:fruit "banana" :color "yellow" :brand "Banana shop"}]
Enter fullscreen mode Exit fullscreen mode

and converting it into {:fruit :color}

({"apple" "red"} {"banana" "yellow"})
Enter fullscreen mode Exit fullscreen mode

Create a hash map from an even list of data

(defn create-hash-map [data] (apply hash-map data))
Enter fullscreen mode Exit fullscreen mode

Output

:> (create-hash-map [1 2 3 4 5 6])
{1 2, 3 4, 5 6}

:> (create-hash-map [1 2 3 4 5])
; Execution error (IllegalArgumentException) at ns/create-hash-map (REPL:31).
; No value supplied for key: 5
Enter fullscreen mode Exit fullscreen mode

Extract a key and value from a map to form a tupled map

(defn tuple [key value data]
  (-> data
      (select-keys [key value])
      (vals)
      (create-hash-map)))
Enter fullscreen mode Exit fullscreen mode

Output

:> (tuple :fruit :color {:fruit "apple" :color "red" :brand "Granny Smith"})
{"apple" "red"}
Enter fullscreen mode Exit fullscreen mode

Map through a list of maps to create tuples for each item.

 (map
  #(tuple :fruit :color %)
  [{:fruit "apple" :color "red" :brand "Granny Smith"}
   {:fruit "banana" :color "yellow" :brand "South African Bananas"}])
Enter fullscreen mode Exit fullscreen mode

Output

({"apple" "red"} {"banana" "yellow"})
Enter fullscreen mode Exit fullscreen mode

Top comments (1)

Collapse
 
camdez profile image
Cameron Desautels

Hi Clarice. đź‘‹ Nice post, and great, clear examples!

One question though—I think it's a little unusual to want data in this format:

({"apple" "red"} {"banana" "yellow"})
Enter fullscreen mode Exit fullscreen mode

...from that original structure. I'm trying to think of a use case...maybe we have a user-defined list of settings that we want to extract or something?

(def settings
  [{:name "PATH"  :val "/"    :scope :api}
   {:name "SHELL" :val "bash" :scope :worker}])

(map (partial tuple :name :val) settings)
;; => ({"PATH" "/"} {"SHELL" "bash"})
Enter fullscreen mode Exit fullscreen mode

But since we're clearly planning to process this sequentially (we used a sequential collection, after all), having maps with a single entry rarely seems useful—we likely don't have known keys to look up (to leverage the associative nature of the collection), and if we're going to consume everything in the map (i.e. the one map entry), then we kinda made it a map for nothing. Hopefully that makes sense. I'd love to know where this would apply, if you have an example!

If you definitely do want to do this, I'd suggest simply:

(def fruit
  [{:fruit "apple" :color "red" :brand "Granny Smith"}
   {:fruit "banana" :color "yellow" :brand "Banana shop"}])

(defn tuple [m k v]
  {(k m) (v m)})

(map #(tuple % :fruit :color) fruit)
;; => ({"apple" "red"} {"banana" "yellow"})
Enter fullscreen mode Exit fullscreen mode

...which will save you a bunch of intermediate map construction / destruction / resultant garbage collection.

(I've changed your argument names here to slightly more idiomatic ones, and changed the order to match conventions as well—functions taking maps typically receive them as the first argument so we can thread them with -> (see assoc, select-keys, etc.) whereas functions taking sequential collections (map, filter, reduce, etc.) typically take them as their final argument so we can thread with ->>. It's a little thing we don't always notice, but tend to appreciate when it makes our code come together neatly!).

In (what I think is) the more common case, where we want to, say, map every fruit to its color, a tidy solution is:

(into {} (map (juxt :fruit :color) fruit))
;; => {"apple" "red", "banana" "yellow"}
Enter fullscreen mode Exit fullscreen mode

Or, if you're comfortable with transducers:

(into {} (map (juxt :fruit :color)) fruit)
;; => {"apple" "red", "banana" "yellow"}
Enter fullscreen mode Exit fullscreen mode

But if you are intending to just process them sequentially (e.g. if we're setting environment variables from those settings above), that's often just:

(map (juxt :name :val) settings)
;; => (["PATH" "/"] ["SHELL" "bash"])
Enter fullscreen mode Exit fullscreen mode

...and then we consume those pair-wise.

Anyway, I hope that's helpful. I find Clojure always has a lesson to teach me about simplicity. Cheers.