【问题标题】:swap keys and values in a map交换地图中的键和值
【发布时间】:2013-03-13 19:45:36
【问题描述】:

是否有交换给定地图的键和值的功能。所以给定一张地图,我希望键成为值,并为键赋值。

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

一种方法是

(zipmap (vals my-map) (keys my-map))

但是想知道 clojure 是否为此提供了实用程序 fn?

【问题讨论】:

  • 这并不总是可行的:{:a 1 :b 1}。如果你想保留信息,那么 sets 键应该变成值:{1 #{:a :b}}.

标签: clojure


【解决方案1】:

对于以后阅读本文的任何人,我认为以下内容应该会有所帮助。

这里有一个小型图书馆https://clojars.org/beoliver/map-inversions

反转映射可能会返回关系。如果映射是单射的(一对一),那么逆映射也是一对一的。如果地图(好像经常是这种情况)是多对一的,那么您应该使用集合或向量。

#Values 被视为原子 ##一对一 地图的值是唯一的

(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}

##多对一 地图的值不是唯一的。这是很常见的——假设你的地图是这种形式是最安全的......所以(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 被视为集合 ##一对多 值是集合/集合,但它们的交集总是空的。 (没有元素出现在两个不同的集合中)

(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

##多对多 值是集合/集合,并且至少存在两个交集不为空的值。如果您的值是集合,那么最好假设它们属于这一类。

(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}

您也可以在 one-to-many 示例中使用 invert-many-to-many

【讨论】:

    【解决方案2】:

    这是一个使用 reduce 可能解决问题的选项:

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

    在函数中

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

    打电话

    (invert {:a 2 b: 4})
    

    然后是reduce-kv(我认为更干净)

    (reduce-kv #(assoc %1 %3 %2) {} {:a 2 :b 4})
    

    【讨论】:

      【解决方案3】:

      这是map-invertclojure.set的目的:

      user=> (clojure.set/map-invert {:a 2 :b 4})
      {4 :b, 2 :a}
      

      【讨论】:

      • 为什么它在 clojure.set 而不是 clojure.core 中?我的意思是我需要注意的任何问题?
      • 也许是历史发展/相对重要性。见(source clojure.set/map-invert) -- 不出意外。
      • 它确实共享集合的不存储重复项的行为 - 只有任何给定 val 的最后一个成为键(其伴随的键作为值)
      • 我遇到了类似的问题,但这些值可能代表多个键。我发现最好的解决方案是(在大约 20 个键的小地图上。 - 比使用 reduce 更好):(->> (group-by second {:a 1 :b 2 :c 1 :d 3 :e 2 :f 1}) (map (fn [[k v]] [k (set (map first v))])) (into {}))
      【解决方案4】:

      clojure.contrib.datalog.util 中有一个函数reverse-map,实现为:

      (defn reverse-map
        "Reverse the keys/values of a map"
        [m]
        (into {} (map (fn [[k v]] [v k]) m)))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-05-25
        • 2011-06-09
        • 1970-01-01
        • 2015-11-21
        • 2018-06-28
        • 2014-03-12
        • 1970-01-01
        相关资源
        最近更新 更多