2012-05-29 44 views
2

我有以下问题所困扰......如何减少此收藏?

鉴于地图集合

[ 
{: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]} 
} 

组由:一(主键)并为其他键积累不同的值。 我可以用蛮力/命令的方式做到这一点,但努力找出如何以clojure的方式解决这个问题。

感谢

+0

在第一张地图条目中,我看到你如何到达{:b [1 2]},但不知道如何到达{:c [1 2]}。它几乎看起来应该是{:c [1 1]}。你能否凭经验陈述算法? – octopusgrabbus

+0

由于:a是主键,前3个映射缩减为1条记录,并且在3个映射中,当其他键的不同值累积时,应该导致:b [1 2]:c [1 2]和: d [1 2 3]。嵌套/递归组是否会这样做? – user922621

回答

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}}} 

会尝试找出明天更精美的做法,现在就要去上睡觉:)

+0

非常感谢。尝试你的真实数据功能,就像一个魅力。现在,我需要剖析你的功能并理解它。 – user922621

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]}} 
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))))) 
+0

感谢您的解决方案。第二个工程。这是第一次看到使用** letfn ** – user922621

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”破坏了group-by。这就是您通过输入“新密钥”对数据进行分区的地方。 “for”生成一个列表 - 这就像Python的列表理解。我们在这里生成一张地图列表,每张地图都有一个关键字,其值是一张地图。首先,我们需要提取相关的密钥。这些密钥保存在“ks”绑定中。我们想要积累不同的价值。尽管我们可以使用reduce来实现这一点,但由于关键字也是函数,所以我们可以使用它们来提取整个集合,然后使用“set”将其减少为不同的值。 “zipmap”将我们的密钥及其相关值连接在一起。然后,在主“for”之外,我们需要将这个地图列表转换为一个映射,其键是“a”的不同值。

+0

感谢您的详细解释。就像您解决方案的优雅和紧凑一样。 – user922621