如何将地图列表转换为嵌套地图?

时间:2019-04-12 15:12:36

标签: clojure transformation

从数据库中获取地图列表(LazySeq)的数据使我需要将其转换为地图地图。

我试图进行“关联”和“合并”,但是由于嵌套的原因并没有带来期望的结果。

这是我的数据的形式:

(def data (list {:structure 1 :cat "A" :item "item1" :val 0.1}
                {:structure 1 :cat "A" :item "item2" :val 0.2}
                {:structure 1 :cat "B" :item "item3" :val 0.4}
                {:structure 2 :cat "A" :item "item1" :val 0.3}
                {:structure 2 :cat "B" :item "item3" :val 0.5}))

我想以表格的形式获取

=> {1 {"A" {"item1" 0.1}
            "item2" 0.2}}
      {"B" {"item3" 0.4}}
    2 {"A" {"item1" 0.3}}
      {"B" {"item3" 0.5}}}

我尝试过

(->> data
     (map #(assoc {} (:structure %) {(:cat %) {(:item %) (:val %)}}))
     (apply merge-with into))

这给

{1 {"A" {"item2" 0.2}, "B" {"item3" 0.4}},
 2 {"A" {"item1" 0.3}, "B" {"item3" 0.5}}}

通过合并,我会丢失一些条目,但是我无法想到其他任何方式。有没有简单的方法?我什至打算尝试使用幽灵。

任何想法都会受到赞赏。

3 个答案:

答案 0 :(得分:3)

如果我要处理嵌套映射,通常首先要考虑的是update-in或assoc-in-这些方法需要一系列嵌套键。对于此类数据非常规则的问题,这很简单。

(assoc-in {} [1 "A" "item1"] 0.1)
;; =>
{1 {"A" {"item1" 0.1}}}

要将序列用于其他事物,reduce是惯用的选择。减少功能恰好在我认为是匿名fn的复杂性级别的边缘,因此为了清楚起见,我将其删除。

(defn- add-val [acc line]
   (assoc-in acc [(:structure line) (:cat line) (:item line)] (:val line)))

(reduce add-val {} data)
;; =>
{1 {"A" {"item1" 0.1, "item2" 0.2}, "B" {"item3" 0.4}},
 2 {"A" {"item1" 0.3}, "B" {"item3" 0.5}}}

我认为这是您想要的效果。

行人路少

由于您的序列来自数据库,因此我不必担心使用瞬态集合来加快聚合速度。而且,现在我想起来了,无论如何,处理嵌套的瞬态图还是很痛苦的。

例如,如果您想使用相同的键来添加任何值,

更新将很方便,但是问题的含义是结构/猫/项目元组是唯一的,因此您只需要分组。< / p>

juxt可用于生成密钥结构-即

((juxt :structure :cat :item) (first data))
[1 "A" "item1"]

但是我尚不清楚,有什么方法可以使用它来使add-val fn更具可读性。

答案 1 :(得分:1)

您可以继续使用现有代码。只有最后的合并必须更改:

(defn deep-merge [& xs]
  (if (every? map? xs)
    (apply merge-with deep-merge xs)
    (apply merge xs)))

(->> data
     (map #(assoc {} (:structure %) {(:cat %) {(:item %) (:val %)}}))
     (apply deep-merge))

;; => 
{1
 {"A" {"item1" 0.1, "item2" 0.2},
  "B" {"item3" 0.4}},
 2
 {"A" {"item1" 0.3},
  "B" {"item3" 0.5}}}

说明:您原来的(apply merge-with into)仅向下合并一级。上方的deep-merge将递归到所有嵌套地图中以进行合并。

附录:@ pete23-​​我可以想到的juxt用途之一是使该函数可重用。例如,我们可以使用juxt提取任意字段,然后将它们转换为嵌套地图(使用另一个函数->nested),最后执行deep-merge

(->> data
     (map (juxt :structure :cat :item :val))
     (map ->nested)
     (apply deep-merge))

其中->nested的实现方式如下:

(defn ->nested [[k & [v & r :as t]]]
  {k (if (seq r) (->nested t) v)})

(->nested [1 "A" "item1" 0.1])
;; => {1 {"A" {"item1" 0.1}}}

一个示例应用程序(按类别求和):

(let [ks [:cat :val]]
  (->> data
       (map (apply juxt ks))
       (map ->nested)
       (apply (partial deep-merge-with +))))
;; => {"A" 0.6000000000000001, "B" 0.9}

注意 deep-merge-with留给我们的读者练习:)

答案 2 :(得分:0)

(defn map-values [f m]
  (into {} (map (fn [[k v]] [k (f v)])) m))

(defn- transform-structures [ss]
  (map-values (fn [cs]
                (into {} (map (juxt :item :val) cs))) (group-by :cat ss)))

(defn transform [data]
  (map-values transform-structures (group-by :structure data)))

然后

(transform data)
相关问题