swap keys and values in a map

后端 未结 4 1057
忘掉有多难
忘掉有多难 2021-02-19 01:47

Is there a function to swap the key and value of a given map. So given a map, I want the keys to become values, and values the keys.

(swap {:a 2 b 4}) => {2 :         


        
相关标签:
4条回答
  • 2021-02-19 02:01

    This is the purpose of map-invert in clojure.set:

    user=> (clojure.set/map-invert {:a 2 :b 4})
    {4 :b, 2 :a}
    
    0 讨论(0)
  • 2021-02-19 02:08

    For anyone reading this at a later date I think the following should be helpful.

    Inverting a map may return a relation. If the map is injective (one-to-one) then the inverse will also be one-to-one. If the map (as if often the case) is many-to-one then you should use a set or vector.

    Values treated as atomic

    one-to-one

    the values of the map are unique

    (defn invert-one-to-one
      "returns a one-to-one mapping"
      [m]
      (persistent! (reduce (fn [m [k v]] (assoc! m v k)) (transient {}) m)))
    
    (def one-to-one {:a 1 :b 2 :c 3})
    
    > (invert-one-to-one one-to-one)
    {1 :a 2 :b 3 :c}
    

    many-to-one

    The values of the map are not unique. This is very common - and it is safest to assume that your maps are of this form... so (def invert invert-many-to-one)

    (defn invert-many-to-one
      "returns a one-to-many mapping"
      ([m] (invert-many-to-one #{} m))
      ([to m]
       (persistent!
        (reduce (fn [m [k v]]
                  (assoc! m v (conj (get m v to) k)))
                (transient {}) m))))
    
    (def many-to-one {:a 1 :b 1 :c 2})
    
    > (invert-many-to-one many-to-one)
    {1 #{:b :a}, 2 #{:c}} ; as expected
    
    > (invert-many-to-one [] many-to-one)
    {1 [:b :a], 2 [:c]} ; we can also use vectors
    
    > (invert-one-to-one many-to-one) ; what happens when we use the 'wrong' function?
    {1 :b, 2 :c} ; we have lost information
    

    Values treated as collections

    one-to-many

    values are sets/collections but their intersections are always empty. (No element occurs in two different sets)

    (defn invert-one-to-many
      "returns a many-to-one mapping"
      [m]
      (persistent!
       (reduce (fn [m [k vs]] (reduce (fn [m v] (assoc! m v k)) m vs))
               (transient {}) m)))
    
    (def one-to-many (invert-many-to-one many-to-one))
    > one-to-many
    {1 #{:b :a}, 2 #{:c}}
    
    > (invert-one-to-many one-to-many)
    {:b 1, :a 1, :c 2} ; notice that we don't need to return sets as vals
    

    many-to-many

    values are sets/collections and there exists at least two values whose intersection is not empty. If your values are collections then it is best to assume that they fall into this category.

    (defn invert-many-to-many
      "returns a many-to-many mapping"
      ([m] (invert-many-to-many #{} m))
      ([to m]
       (persistent!
        (reduce (fn [m [k vs]]
                  (reduce (fn [m v] (assoc! m v (conj (get m v to) k))) m vs))
                (transient {}) m))))
    
    (def many-to-many {:a #{1 2} :b #{1 3} :c #{3 4}})
    
    > (invert-many-to-many many-to-many)
    {1 #{:b :a}, 2 #{:a}, 3 #{:c :b}, 4 #{:c}}
    
    ;; notice that there are no duplicates when we use a vector
    ;; this is because each key appears only once
    > (invert-many-to-many [] many-to-many)
    {1 [:a :b], 2 [:a], 3 [:b :c], 4 [:c]}
    
    > (invert-many-to-one many-to-many)
    {#{1 2} #{:a}, #{1 3} #{:b}, #{4 3} #{:c}}
    
    > (invert-one-to-many many-to-many)
    {1 :b, 2 :a, 3 :c, 4 :c}
    
    > (invert-one-to-one many-to-many)
    {#{1 2} :a, #{1 3} :b, #{4 3} :c} ; this would be missing information if we had another key :d mapping to say #{1 2}
    

    You could also use invert-many-to-many on the one-to-many example.

    0 讨论(0)
  • 2021-02-19 02:09

    There's a function reverse-map in clojure.contrib.datalog.util, it's implemented as:

    (defn reverse-map
      "Reverse the keys/values of a map"
      [m]
      (into {} (map (fn [[k v]] [v k]) m)))
    
    0 讨论(0)
  • 2021-02-19 02:13

    Here is an option that may fit the problem using reduce:

    (reduce #(assoc %1 (second %2) (first %2)) {} {:a 2 :b 4})
    

    Here in a function

    (defn invert [map]
      (reduce #(assoc %1 (second %2) (first %2)) {} map))
    

    Calling

    (invert {:a 2 b: 4})
    

    Then there is the reduce-kv (cleaner in my opinion)

    (reduce-kv #(assoc %1 %3 %2) {} {:a 2 :b 4})
    
    0 讨论(0)
提交回复
热议问题