Neoasimov
Neoasimov

Reputation: 1111

Clojure: creating a map with switched keys and values from another map

I am trying to create a kind of a reverted index from an input map. The input map I got is:

{"id-1" {"values" ["a" "b" "c"]}, "id-2" {"values" ["a" "b" "c"]}}

Then I want to have this other map as result:

{"a" ["id-1" "id-2"], "b" ["id-1" "id-2"], "c" ["id-1" "id-2"]}

However, I think that my mind did go crazy, and I think I painted myself into the corner without being able of thinking out of the box. Here is what I got so far, and it looks like that it stinks:

(->> {"id-1" {"values" ["a" "b" "c"]} "id-2" {"values" ["a" "b" "c"]}}
       (map #(->> (get (second %) "values")
              (map (fn [x y] (hash-map y x)) (into [] (repeat (count (get (second %) "values")) (first %))))
              (apply merge-with concat)))
       (apply merge-with concat))

Basically, I use a first map used to "iterate" over all my input values. Then I use a second map to create a series of individual maps that looks like that:

({"a" "id-2"} {"b" "id-2"} {"c" "id-2"} {"a" "id-1"} {"b" "id-1"} {"c" "id-1"})

To get to that map, I create an intermediary array using into [] (repeat ..) to feed to the map along with the array of values.

Then I merge them together to get my expected value.

Two issues here:

  1. This seems really much more complex than I have the intuition that it is
  2. The current end result is not yet perfect since I am getting this:

    {"a" (\i \d - \1 \i \d - \2), "b" (\i \d - \1 \i \d - \2), "c" (\i \d - \1 \i \d - \2)}

Upvotes: 1

Views: 715

Answers (2)

A. Webb
A. Webb

Reputation: 26436

Using map destructuring:

(apply merge-with into (for [[k {vs "values"}] input, v vs] {v [k]}))

Clearer

(apply merge-with into
  (for [[k m] input
        v (get m "values")] 
    {v [k]}))

Upvotes: 8

sloth
sloth

Reputation: 101032

Given this input:

(def input {"id-1" {"values" ["a" "b" "c"]}, "id-2" {"values" ["a" "b" "c"]}})

it's easier to do:

(defn extract [key values]
  (for [v (get values "values")] {v [key]}))

(->> input 
    (mapcat (partial apply extract))
    (apply merge-with concat))

or, without an additional function:

(->> (for [[k vs] input]
        (for [v (get vs "values")] {v [k]}))
     (flatten)
     (apply merge-with concat))

which works the way you intended.

The trick is to wrap key in a vector in the extract function so merge-with concat works without concatenating the strings.

Upvotes: 2

Related Questions