Most graceful way for a lists -> maps transposition

I feel I’ll be enlightened by some core Clojure way to do this. It can be done with some applies and several layers of “map” or “zipmap” or in one student solution “hashmap”, but there’s got to be a clojure.core solution I’m missing.

(let [[names jobs langs :as all] [["john" "jane" "michael"]
				  ["chef" "driver" "vet"]
				  ["English" "German" "French"]]]
  (list {:name "john" :job "chef" :lang "English"}
	;; the other two likewise in this list
	))

Here’s one solution, but I don’t feel it’s very robust:

(let [[names jobs langs :as all] [["john" "jane" "michael"]
					["chef" "driver" "vet"]
					["English" "German" "French"]]]
	(map #(zipmap [:name :job :lang] %)
		(partition 3 
			   (interleave names jobs langs))))
;; ({:name "john", :job "chef", :lang "English"} 
;;  {:name "jane", :job "driver", :lang "German"} 
;;  {:name "michael", :job "vet", :lang "French"})

How about giving names jobs langs as args 2-4 to map and [%1 %2 %3] as second argument to zipmap?

user> (let [[names jobs langs :as all] [["john" "jane" "michael"]
					["chef" "driver" "vet"]
					["English" "German" "French"]]]
        (apply map 
         (fn [ & xs] (zipmap [:name :job :lang] xs))
         all))
({:name "john", :job "chef", :lang "English"}
 {:name "jane", :job "driver", :lang "German"}
 {:name "michael", :job "vet", :lang "French"})
1 Like
(let [all [["john" "jane" "michael"]
            ["chef" "driver" "vet"]
            ["English" "German" "French"]]]
  (apply map (comp (partial zipmap [:name :job :lang]) vector) all))

=>
({:name “john”, :job “chef”, :lang “English”}
{:name “jane”, :job “driver”, :lang “German”}
{:name “michael”, :job “vet”, :lang “French”})

3 Likes
(->>  [["john" "jane" "michael"]
       ["chef" "driver" "vet"]
       ["English" "German" "French"]]
      (apply map (fn [name job lang]
                   `{:name ~name :job ~job :lang ~lang})))

(defmacro from-keys [ks]
  (let [args (map (comp gensym name) ks)]
    `(fn [[email protected]]
       (hash-map [email protected](interleave ks args)))))

(->>  [["john" "jane" "michael"]
       ["chef" "driver" "vet"]
       ["English" "German" "French"]]
      (apply map (from-keys [:name :job :lang])))

If you want more programmatic transforms where you don’t specify the keys, the zipmap solutions are better.

2 Likes

Just curious, is there a reason you quoted the map in your first example?

This seems to work just fine

(let [[names jobs langs :as all] [["john" "jane" "michael"]
	    			              ["chef" "driver" "vet"]
		         		          ["English" "German" "French"]]]
  (map (fn [n j l] {:name n :job j :lang l}) names jobs langs))
5 Likes

This would be my suggestion as well, using the fact that map is multi-arity.

I love it.

Idea: The anonymous function could be:

 #(zipmap [:name :job :lang] %&)

So, by using apply just to reduce the number of characters :D, one more way to do it could be:

(apply map #(zipmap [:name :job :lang] %&) all)
1 Like

Late night writing. Building the map with quoting is not useful or necessary.

1 Like

Most excellent answers – definitely some great learning for me!

(let [[names jobs langs :as all] [["john" "jane" "michael"]
				                 ["chef" "driver" "vet"]
				                 ["English" "German" "French"]]]
	(apply map #(zipmap [:name :job :lang] %&) all))
;; => ({:name "john", :job "chef", :lang "English"} {:name "jane", :job "driver", :lang "German"} {:name "michael", :job "vet", :lang "French"})

Post:

2 Likes

I would break it down in to 2 simple steps:

  1. Transpose the data matrix
  2. Apply the labels to make an entity-map
(ns tst.demo.core
  (:use tupelo.core tupelo.test))

(def raw-data
  [["john" "jane" "michael"]
   ["chef" "driver" "vet"]
   ["English" "German" "French"]])

(dotest
  (let [data-by-entity (apply mapv vector raw-data) ; transpose the array data
        entity-maps    (mapv ; convert each entity-vec to an enitty-map
                         #(zipmap [:name :job :lang] %)
                         data-by-entity)]
    (is= data-by-entity
      [["john" "chef" "English"]
       ["jane" "driver" "German"]
       ["michael" "vet" "French"]])
    (is= entity-maps
      [{:name "john", :job "chef", :lang "English"}
       {:name "jane", :job "driver", :lang "German"}
       {:name "michael", :job "vet", :lang "French"}])))
2 Likes