在Clojure中,如何分组元素?

时间:2012-02-22 10:50:17

标签: clojure group-by aggregate-functions

在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)])

是否有更多惯用解决方案?

4 个答案:

答案 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)