【问题标题】:Can I "transpose" a list of maps into a map of lists in Clojure?我可以将地图列表“转置”为 Clojure 中的列表地图吗?
【发布时间】:2011-10-23 05:18:03
【问题描述】:

嗨,伙计们:我想为地图中的所有值绘制一个“平均值”。说我有一张地图清单:

[{"age" 2 "height" 1 "weight" 10},
{"age" 4 "height" 4 "weight" 20},
{"age" 7 "height" 11 "weight" 40}]

我想要的输出是

{"age 5 "height" 5 ....}

///下面是我脑子里乱七八糟的东西,即我想象的在 Clojure 中工作的方式......不要太认真

转置列表:

  {"age" [2 4 7] "height" [1 4 11] } 

然后我可以简单地做一些类似的事情(再次,在这里组成一个名为 freduce 的函数)

  (freduce average (vals (map key-join list)))

得到

{"age" 5 "weight" 10 "height" 7}

【问题讨论】:

  • 您将要减少,这样您就不必遍历序列两次。您将从一个空映射开始作为您的累加器,当您的 reduce 到达每个映射时,将所有值与累加器中的相应值相加。在列表的最后一个元素上,将每个值除以列表的长度。
  • 我实际上想对数据做更复杂的数学运算(标准差,...),所以我想我想将数据合并的方式与转置的方式解耦。

标签: list map clojure transpose


【解决方案1】:

创建向量图:

(减少 (fn [m [k v]] (assoc m k (conj (get m k []) v))) {} (应用 concat list-of-maps))

创建平均值图:

(减少 (fn [m [k v]] (assoc m k (/ (reduce + v) (count v)))) {} 向量图)

【讨论】:

  • 感谢您的解释...有道理。
【解决方案2】:

看看merge-with

这是我的一些实际代码:

(let [maps [{"age" 2 "height" 1 "weight" 10},
            {"age" 4 "height" 4 "weight" 20},
            {"age" 7 "height" 11 "weight" 40}]]
  (->> (apply merge-with #(conj %1 %2)
             (zipmap (apply clojure.set/union (map keys maps))
                     (repeat [])) ; set the accumulator
             maps)
       (map (fn [[k v]] [k (/ (reduce + v) (count v))]))
       (into {})))

【讨论】:

  • 这很优雅,但在这一点上对我来说可能有点太高级了。感谢您将我指向合并,我将学习它...
【解决方案3】:

这是一个相当冗长的解决方案。希望有人能想出更好的东西:

(let [maps [{"age" 2 "height" 1 "weight" 10},
            {"age" 4 "height" 4 "weight" 20},
            {"age" 7 "height" 11 "weight" 40}]
      ks (keys (first maps))
      series-size (count maps)
      avgs (for [k ks]
             (/ (reduce +
                        (for [m maps]
                          (get m k)))
                series-size))]
  (zipmap ks avgs))

【讨论】:

  • 我认为你的解决方案是正确的,因为它非常易读。
【解决方案4】:
(defn key-join [map-list]
  (let [keys (keys (first map-list))]
       (into {} (for [k keys] [k (map #(% k) map-list)]))))
(defn mapf [f map]
  (into {} (for [[k v] map ] [k (f v)])))
(defn average [col]
  (let [n (count col)
        sum (apply + col)]
       (/ sum n)))

演示

user=> (def data-list [{"age" 2 "height" 1 "weight" 10},
{"age" 4 "height" 4 "weight" 20},
{"age" 7 "height" 11 "weight" 40}])
#'user/data-list
user=> (key-join data-list)
{"age" (2 4 7), "height" (1 4 11), "weight" (10 20 40)}
user=> (mapf average (key-join data-list))
{"age" 13/3, "height" 16/3, "weight" 70/3}

【讨论】:

  • 似乎有道理。不确定平均值应用在哪里......我猜是mapf >?
  • 如果是函数名的话,随便改写就好了。
  • >不确定平均值适用于何处。平均值是应用地图数据值(集合)。 “平均”不会应用于地图键。更不用说减少(!)平均地图数据。
【解决方案5】:

这是另一个使用 merge-with 而没有 zipmap 的版本。

(let [data [{:a 1 :b 2} {:a 2 :b 4} {:a 4 :b 8}]
           num-vals (count data)]
     (->> data (apply merge-with +) 
          (reduce (fn [m [k v]] (assoc m k (/ v num-vals))) {})))

【讨论】:

  • 这假设所有键都存在于所有地图中,这可能是一个没有根据的假设(至少不明确)。
  • 您在密钥假设上是对的,如果某些地图缺少密钥,您需要将合并中的 + 替换为 #(+ (or %1 0) (or %2 0))
【解决方案6】:

这是我的单线解决方案:

(def d [{"age" 2 "height" 1 "weight" 10},
   {"age" 4 "height" 4 "weight" 20},
   {"age" 7 "height" 11 "weight" 40}])

(into {} (map (fn [[k v] [k (/ v (count d))]]) (apply merge-with + d)))

=> {"height" 16/3, "weight" 70/3, "age" 13/3}

逻辑如下:

  • 在地图上使用 merge-with + 来计算每个键值的总和
  • 将所有结果值除以地图总数以获得平均值
  • 将结果放回带有 (into {} ...) 的哈希图中

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-06
    • 1970-01-01
    • 2021-06-21
    相关资源
    最近更新 更多