10

I have data that looks like this

(def a [{:firmAccount "MSFT" :Val 10  :PE 3 }  
        {:firmAccount "MSFT" :Val 15  :PE 4} 
        {:firmAccount "GOG" :Val 15 :PE 3} 
        {:firmAccount "YAH" :Val 8 :PE 1}])

I want to group by on :firmAccount and then SUM the :Val and :PE for each firm account and get something like

 [{:firmAccount "MSFT" :Val 25 :PE 7}
  {:firmAccount "GOG" :Val 15 :PE 3}    
  {:FirmAccount "YAH" :Val 8 :PE 1}]

It is really a trivial thing and in SQL I would not even think twice but since I am learning clojure please bear with me

Nathan Davis
  • 5,636
  • 27
  • 39
Ash
  • 2,531
  • 2
  • 29
  • 38

4 Answers4

8

Clojure.core has a built-in group-by function. The solution becomes a little ugly by the presence of both text and int vals in the maps.

(for [m (group-by :firmAccount a)]
   (assoc (apply merge-with + (map #(dissoc % :firmAccount) (val m)))
          :firmAccount (key m)))
Nathan Davis
  • 5,636
  • 27
  • 39
mac
  • 9,885
  • 4
  • 36
  • 51
  • 1
    Thanks a lot. It works well for me and it is very succinct. Also the answer made me aware of how to use assoc , merge-with , apply , dissoc , group-by and map in one single example which is awesome . – Ash Aug 26 '11 at 21:44
6

And for completeness here's an alternate version that uses map:

(map (fn [[firmAccount vals]] 
   {:firmAccount firmAccount 
    :Val (reduce + (map :Val vals)) 
    :PE (reduce + (map :PE vals))}) 
  (group-by :firmAccount a))
user499049
  • 564
  • 2
  • 6
1

Try creating a new map array or map of maps with the same structure. You can write a function to add elements to this new map that sums that fields if the :firm-account exists. Maybe a map like this?

(def a {"MSFT" {:Val 25  :PE 7 }
        "GOG" {:Val 15 :PE 3} 
        "YAH" {:Val 8 :PE 1}})

With a personalized add function like:

(add-to-map [element map]
  (if (contains? (find-name element))
    {map (add-fields element (find (find-name element)))}
    {map element}))
Nathan Davis
  • 5,636
  • 27
  • 39
Pedro Montoto García
  • 1,672
  • 2
  • 18
  • 38
  • Well, if you use the add function I sketched here you don't need to change the structure. Use this ideas how you please. – Pedro Montoto García Aug 26 '11 at 11:57
  • The change is necessary due to the datastructure in the question. The structure you use in your answer is the requested answer, not the required input. – mac Aug 26 '11 at 17:39
0

It's a matter of style, but I find using thread-last macro (->>) is easier to read.

(->> a
     (group-by :firmAccount)
     (map (fn [[firmAccount vals]]
             {:firmAccount firmAccount 
              :Val (reduce + (map :Val vals)) 
              :PE (reduce + (map :PE vals))})