【问题标题】:In Clojure, how to group elements?在 Clojure 中,如何对元素进行分组?
【发布时间】:2012-03-12 16:42:29
【问题描述】:

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

有没有更惯用的解决方案?

【问题讨论】:

  • 您提出的解决方案中的变量名具有误导性。解构为“fruit”的值实际上是一系列时刻-水果对向量。
  • 非常感谢!更新了问题

标签: clojure group-by aggregate-functions


【解决方案1】:

不要太快放弃 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】:

    我遇到过类似的分组问题。通常我最终会在一些 seq 处理步骤中插入 merge-with 或 update-in:

    (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)
    

    这两者都感觉“更实用”,但也感觉有点复杂。不要太快放弃您的解决方案,它易于理解且功能齐全。

    【讨论】:

    • 你觉得(apply merge-with (comp flatten list) (map (partial apply hash-map) data))怎么样?
    • 这是一个很好、简洁的解决方法。我相信flattenO(n),因此在某些数据集中重复应用它可能效果不佳。
    • 你是对的。我找到了更好的解决方案,请参阅我的答案。顺便说一句,有没有与agg 一样的内置函数?
    【解决方案3】:

    思考@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)}
    

    【讨论】:

      【解决方案4】:

      也许只是稍微修改一下标准分组:

      (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)
      

      【讨论】:

        猜你喜欢
        • 2012-11-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-13
        相关资源
        最近更新 更多