从数据库中获取数据作为地图列表(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}}}
通过合并我丢失了一些条目,但我想不出任何其他方式。有一个简单的方法吗?我甚至打算尝试使用幽灵。
任何想法将不胜感激。
如果我正在处理嵌套映射,第一站通常是考虑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}}}
我认为这是你正在寻找的效果。
少走过的路:
由于您的序列来自数据库,我不担心使用瞬态集合来加速汇总。此外,现在我想一想,处理嵌套瞬态贴图无论如何都是一种痛苦。
例如,如果你想用相同的键添加任何值,update-in会很方便,但你的问题的含义是结构/ cat / item元组是唯一的,所以你只需要分组。
juxt可用于生成密钥结构 - 即
((juxt :structure :cat :item) (first data))
[1 "A" "item1"]
但是我不清楚是否有任何方法可以使用它来使add-val fn更具可读性。
您可以继续使用现有代码。只有最后的合并必须改变:
(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
留给我们的读者练习:)
(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)