如何减少这个集合?

时间:2012-05-29 21:18:22

标签: clojure

我正在努力解决以下问题...

给出一组地图

[
 {:a 1 :b 1 :c 1 :d 1}
 {:a 1 :b 2 :c 1 :d 2}
 {:a 1 :b 2 :c 2 :d 3}
 {:a 2 :b 1 :c 1 :d 5}
 {:a 2 :b 1 :c 1 :d 6}
 {:a 2 :b 1 :c 1 :d 7}
 {:a 2 :b 2 :c 1 :d 7}
 {:a 2 :b 3 :c 1 :d 7}
]

希望减少/转换为......

{
 1 {:b [1 2] :c [1 2] :d [1 2 3]}
 2 {:b [1 2 3] :c 1 :d [5 6 7]}
}

group-by:a(主键)并累积其他键的不同值。 我可以用蛮力/势在必行的方式做到这一点,但努力弄清楚如何以clojure方式解决这个问题。

由于

4 个答案:

答案 0 :(得分:3)

这是一个公认的不优雅的初稿解决方案:

(defn reducing-fn [list-of-maps grouping-key]
    (reduce (fn [m [k lst]]
              (assoc m k (dissoc (reduce (fn [m1 m2]
                                           (apply hash-map
                                                  (apply concat
                                                         (for [[k v] m2]
                                                           [k (conj (get m1 k #{}) v)]))))
                                         {}
                                         lst)
                                 grouping-key)))
            {}
            (group-by #(grouping-key %) list-of-maps)))

user> (reducing-fn [{:a 1 :b 1 :c 1 :d 1}
                    {:a 1 :b 2 :c 1 :d 2}
                    {:a 1 :b 2 :c 2 :d 3}
                    {:a 2 :b 1 :c 1 :d 5}
                    {:a 2 :b 1 :c 1 :d 6}
                    {:a 2 :b 1 :c 1 :d 7}
                    {:a 2 :b 2 :c 1 :d 7}
                    {:a 2 :b 3 :c 1 :d 7}] 
                   :a)
=> {2 {:c #{1}, :b #{1 2 3}, :d #{5 6 7}}, 1 {:c #{1 2}, :b #{1 2}, :d #{1 2 3}}}

明天会试着找出更加精致的方法,现在就去睡觉了:)

答案 1 :(得分:2)

(use 'clojure.set)
(def data
  [
   {:a 1 :b 1 :c 1 :d 1}
   {:a 1 :b 2 :c 1 :d 2}
   {:a 1 :b 2 :c 2 :d 3}
   {:a 2 :b 1 :c 1 :d 5}
   {:a 2 :b 1 :c 1 :d 6}
   {:a 2 :b 1 :c 1 :d 7}
   {:a 2 :b 2 :c 1 :d 7}
   {:a 2 :b 3 :c 1 :d 7}
  ]
)

(defn key-join
  "join of map by key , value is distinct."
  [map-list]
  (let [keys (keys (first map-list))]
       (into {} (for [k keys] [k (vec (set (map #(% k) map-list)))]))))

(defn group-reduce [key map-list]
  (let [sdata (set map-list)
        group-value (project sdata [key])]
       (into {}
         (for [m group-value] [(key m) (key-join (map #(dissoc % key) (select #(= (key %) (key m)) sdata)))]))))
;;other version fast than group-reduce 
(defn gr [key map-list]
  (let [gdata (group-by key map-list)]
    (into {} (for [[k m] gdata][k (dissoc (key-join m) key)]))))
user=> (group-reduce :a data)
{1 {:c [1 2], :b [1 2], :d [1 2 3]}, 2 {:c [1], :b [1 2 3], :d [5 6 7]}}
user=> (gr :a data)
{1 {:c [1 2], :b [1 2], :d [1 2 3]}, 2 {:c [1], :b [1 2 3], :d [5 6 7]}}

答案 2 :(得分:2)

另一种解决方案:

(defn pivot [new-key m]
  (apply merge 
    (for [[a v] (group-by new-key m)]
      {a (let [ks (set (flatten (map keys (map #(dissoc % new-key) v))))]
            (zipmap ks (for [k ks] (set (map k v)))))})))

ETA:新密钥将是:这里的一个键,m是您的输入映射。

第一个“for”破坏了分组。这就是您通过输入“new-key”对数据进行分区的地方。 “for”生成一个列表 - 就像Python的列表理解一样。在这里,我们生成一个地图列表,每个地图都有一个键,其值为地图。首先,我们需要提取相关的密钥。这些键保存在“ks”绑定中。我们想积累不同的价值观。虽然我们可以使用reduce来实现这一点,因为关键字也是函数,我们可以使用它们来提取整个集合,然后使用“set”来减少到不同的值。 “zipmap”将我们的密钥及其相关值联系在一起。然后在主“for”之外,我们需要将这个地图列表转换为单个地图,其键是“a”的不同值。

答案 3 :(得分:1)

另一种解决方案:

(defn transform
  [key coll]
  (letfn [(merge-maps
            [coll]
            (apply merge-with (fnil conj #{}) {} coll))
          (process-key
            [[k v]]
            [k (dissoc (merge-maps v) key)])]
    (->> coll
      (group-by #(get % key))
      (map process-key)
      (into (empty coll)))))

代码未经测试。

编辑:当然它不起作用,因为merge-with试图太聪明。

(defn transform
  [key coll]
  (letfn [(local-merge-with
            [f m & ms]
            (reduce (fn [m [k v]] (update-in m [k] f v))
                    m
                    (for [m ms e m] e)))
          (merge-maps
            [coll]
            (apply local-merge-with (fnil conj #{}) {} coll))
          (process-key
            [[k v]]
            [k (dissoc (merge-maps v) key)])]
    (->> coll
      (group-by #(get % key))
      (map process-key)
      (into (empty coll)))))