在clojure中,我想汇总这些数据:
(def data [[:morning :pear][:morning :mango][:evening :mango][:evening :pear]])
(group-by first data)
;{:morning [[:morning :pear][:morning :mango]],:evening [[:evening :mango][:evening :pear]]}
我的问题是:evening
和:morning
是多余的。
相反,我想创建以下集合:
([:morning (:pear :mango)] [:evening (:mango :pear)])
我想出了:
(for [[moment moment-fruit-vec] (group-by first data)] [moment (map second moment-fruit-vec)])
是否有更多惯用解决方案?
答案 0 :(得分:5)
我遇到了类似的分组问题。通常我最终会将merge-with或update-in插入到某个seq处理步骤中:
(apply merge-with list (map (partial apply hash-map) data))
你得到一张地图,但这只是一系列键值对:
user> (apply merge-with list (map (partial apply hash-map) data))
{:morning (:pear :mango), :evening (:mango :pear)}
user> (seq *1)
([:morning (:pear :mango)] [:evening (:mango :pear)])
但是,如果每个键出现两次,此解决方案只能获得您想要的效果。这可能会更好:
(reduce (fn [map [x y]] (update-in map [x] #(cons y %))) {} data)
这些都感觉“功能性更强”,但也有点令人费解。不要过于迅速地解雇您的解决方案,它易于理解且功能足够。
答案 1 :(得分:4)
不要太快解除group-by
,它已按所需的密钥汇总您的数据,它没有更改数据。期望一系列时刻 - 果实对的任何其他函数将接受group-by
返回的地图中查找的任何值。
在计算摘要方面,我倾向于达到merge-with
,但为此我必须将输入数据转换为一系列地图,并使用所需的键构建“基本地图”并清空矢量作为价值观。
(let [i-maps (for [[moment fruit] data] {moment fruit})
base-map (into {}
(for [key (into #{} (map first data))]
[key []]))]
(apply merge-with conj base-map i-maps))
{:morning [:pear :mango], :evening [:mango :pear]}
答案 2 :(得分:2)
冥想@mike t的回答,我想出了:
(defn agg[x y] (if (coll? x) (cons y x) (list y x)))
(apply merge-with agg (map (partial apply hash-map) data))
当密钥在data
上显示两次以上时,此解决方案也有效:
(apply merge-with agg (map (partial apply hash-map)
[[:morning :pear][:morning :mango][:evening :mango] [:evening :pear] [:evening :kiwi]]))
;{:morning (:mango :pear), :evening (:kiwi :pear :mango)}
答案 3 :(得分:0)
可能只是稍微修改了标准分组:
(defn my-group-by
[fk fv coll]
(persistent!
(reduce
(fn [ret x]
(let [k (fk x)]
(assoc! ret k (conj (get ret k []) (fv x)))))
(transient {}) coll)))
然后将其用作:
(my-group-by first second data)