Uniform cost search in clojure - clojure

there seems to be a bug in this program I appreciate if someone could help
(defn findmincostindex [frontier]
(loop [i 0 n 0]
(if (< i (count frontier))
(if (< (:cost (get frontier i)) (:cost (get frontier n)))
(recur (inc i) i)
(recur (inc i) n))
n)))
(defn uniformcostsearch [graph start end]
((fn [frontier explored]
(if (empty? frontier)
"Failure"
(let [pathwithmincost (findmincostindex (into [] frontier))
;(let [pathwithmincost (findmincostindex frontier)
path (:path (get frontier pathwithmincost))
cost (:cost (get frontier pathwithmincost))
node (peek path)
childs (keys (graph node))]
(if (= node end)
path
(recur
(concat (subvec frontier 0 pathwithmincost) (subvec frontier (inc pathwithmincost))
(map (fn [c] {:path (conj path c) :cost (+ cost ((graph node) c))})
(filter #(not (contains? explored %)) childs)))
(conj explored node))))))
[{:path [start] :cost 0}] #{}))
(def graph {
"Oradea" {
"Zerind" 71,
"Sibiu" 151
},
"Zerind" {
"Oradea" 71,
"Arad" 75
},
"Arad" {
"Zerind" 75,
"Sibiu" 140,
"Timisoara" 118
},
"Sibiu" {
"Oradea" 151,
"Arad" 140,
"Fagaras" 99,
"Rimnicu Vilcea" 80
},
"Fagaras" {
"Sibiu" 99,
"Bucharest" 211
},
"Rimnicu Vilcea" {
"Sibiu" 80,
"Pitesti" 97,
"Craiova" 146
},
"Timisoara" {
"Arad" 118,
"Lugoj" 111
},
"Lugoj" {
"Timisoara" 111,
"Mehadia" 70
},
"Pitesti" {
"Rimnicu Vilcea" 97,
"Craiova" 138,
"Bucharest" 101
},
"Mehadia" {
"Lugoj" 70,
"Drobeta" 75
},
"Drobeta" {
"Mehadia" 75,
"Craiova" 120
},
"Craiova" {
"Drobeta" 120,
"Rimnicu Vilcea" 146,
"Pitesti" 138
},
"Bucharest" {
"Pitesti" 101,
"Fagaras" 211,
"Giurgiu" 90,
"Urziceni" 85
},
"Giurgiu" {
"Bucharest" 90
},
"Urziceni" {
"Bucharest" 85,
"Vaslui" 142,
"Hirsova" 98
},
"Hirsova" {
"Urziceni" 98,
"Eforie" 86
},
"Eforie" {
"Hirsova" 86
},
"Vaslui" {
"Iasi" 92,
"Urziceni" 142
},
"Iasi" {
"Neamt" 87,
"Vaslui" 92
},
"Neamt" {
"Iasi" 87
}})
(println (uniformcostsearch graph "Neamt" "Iasi"))
(println (uniformcostsearch graph "Neamt" "Vaslui"))
(println (uniformcostsearch graph "Bucharest" "Arad"))
it should output these lines
['Neamt', 'Iasi']
['Neamt', 'Iasi', 'Vaslui']
['Bucharest', 'Pitesti', 'Rimnicu Vilcea', 'Sibiu', 'Arad']
but instead it says:
clojure.lang.LazySeq cannot be cast to
clojure.lang.IPersistentVector
when I use
(into [] frontier)
if I use frontier alone it says
java.lang.NullPointerException

The exception happens in the first subvec in your recur. You're recurring with the result of concat, which is a lazy sequence, and you can't take a subvector of a lazy sequence. A quick fix is to just wrap it in vec:
(vec (concat (subvec frontier 0 pathwithmincost) (subvec frontier (inc pathwithmincost))
(map (fn [c] {:path (conj path c) :cost (+ cost ((graph node) c))})
(remove explored childs))))
A few other tips:
findmincostindex is essentially a reimplementation of min-key which is less general, and you could probably make this cleaner by using that.
Sets are functions too, and return the argument (truthy values) if it is a member of the set. You can use this to improve a little on (filter #(not (contains? explored %)) childs))) - for instance (remove explored childs)
Your let could be made a bit shorter by using destructuring.
Here's my attempt:
(let [[idx {:keys [path cost]}] (apply min-key (comp :cost second) (map-indexed vector frontier))
node (peek path)
childs (keys (graph node))]
The process here is
(map-indexed vector frontier) makes the frontier into pairs of index and node.
min-key finds the pair which has the lowest value for (:cost (second pair)).
let binds the name idx to the index of that pair, and path and cost to the :path and :cost keys of the node.

Related

How to schedule a list of functions `n` seconds apart with Clojure Core Async

In my Clojure project I'm trying to make a list of http calls to an API that has a rate limiter that only allows n calls per minute. I want each of the responses to be returned once all the http calls are finished for further processing. I am new to Clojure's Core Async, but thought it would be a good fit, but because I need to run each call n seconds apart I am also trying to use the Chime library. In Chime's library it has examples using Core Async, but the examples all call the same function at each time interval which won't work for this use case.
While there is probably a way to use chime-async that better serves this use case, all of my attempts at that have failed so I've tried simply wrapping Chime calls with core async, but I am probably more baffled by Core Async than Chime.
This is an example of my name space.
(ns mp.util.schedule
(:require [chime.core :as chime]
[clojure.core.async :as a]
[tick.alpha.api :as tick]))
(defn schedule-fns
"Takes a list of functions and a duration in seconds then runs each function in the list `sec` seconds apart
optionally provide an inst to start from"
[fs sec & [{:keys [inst] :or {inst (tick/now)}}]]
(let [ch (a/chan (count fs))
chime-times (map-indexed
(fn mapped-fn [i f]
(a/put! ch (chime/chime-at [(.plusSeconds inst (* i sec))]
(fn wrapped-fn [_] (f)))))
fs)]
(doseq [chi chime-times]
(a/<!! chi))))
; === Test Code ===
; simple test function
(defn sim-fn
"simple function that prints a message and value, then returns the value"
[v m]
(println m :at (tick/now))
v)
; list of test functions
(def fns [#(sim-fn 1 :one)
#(sim-fn 2 :two)
#(sim-fn 3 :three)])
What I want to happen when calling (schedule-fns fns 2) is for each function in fns to run n seconds from each other and for schedule-fns to return (1 2 3) (the return values of the functions), but this isn't what it is doing. It is calling each of the functions at the correct times (which I can see from the log statements) but it isn't returning anything and there's an error I don't understand. I'm getting:
(schedule-fns fns 2)
:one :at #time/instant "2021-03-05T23:31:52.565Z"
Execution error (IllegalArgumentException) at clojure.core.async.impl.protocols/eval11496$fn$G (protocols.clj:15).
No implementation of method: :take! of protocol: #'clojure.core.async.impl.protocols/ReadPort found for class: java.lang.Boolean
:two :at #time/instant "2021-03-05T23:31:54.568Z"
:three :at #time/instant "2021-03-05T23:31:56.569Z"
If I could get help getting my code to use Core Async properly (with or without Chime) I'd really appreciate it. Thanks.
Try this:
(defn sim-fn
"simple function that prints a message and value, then returns the value"
[v m]
(println m)
v)
; list of test functions
(def fns [#(sim-fn 1 :one)
#(sim-fn 2 :two)
#(sim-fn 3 :three)])
(defn schedule-fns [fns sec]
(let [program (interpose #(Thread/sleep (* sec 1000))
fns)]
(remove #(= % nil)
(for [p program]
(p)))))
Then call:
> (schedule-fns fns 2)
:one
:two
:three
=> (1 2 3)
I came up with a way to get what I want...with some caveats.
(def results (atom []))
(defn schedule-fns
"Takes a list of functions and a duration in seconds then runs each function in the list `sec` seconds apart
optionally provide an inst to start from"
[fs sec]
(let [ch (chan (count fs))]
(go-loop []
(swap! results conj (<! ch))
(recur))
(map-indexed (fn [i f]
(println :waiting (* i sec) :seconds)
(go (<! (timeout (* i sec 1000)))
(>! ch (f))))
fs)))
This code has the timing and behavior that I want, but I have to use an atom to store the responses. While I can add a watcher to determine when all the results are in, I still feel like I shouldn't have to do that.
I guess I'll use this for now, but at some point I'll keep working on this and if anyone has something better than this approach I'd love to see it.
I had a couple friends look at this and they each came up with different answers. These are certainly better than what I was doing.
(defn schedule-fns [fs secs]
(let [ret (atom {})
sink (a/chan)]
(doseq [[n f] (map-indexed vector fs)]
(a/thread (a/<!! (a/timeout (* 1000 n secs)))
(let [val (f)
this-ret (swap! ret assoc n val)]
(when (= (count fs) (count this-ret))
(a/>!! sink (mapv (fn [i] (get this-ret i)) (range (count fs))))))))
(a/<!! sink)))
and
(defn schedule-fns
[fns sec]
(let [concurrent (count fns)
output-chan (a/chan)
timedout-coll (map-indexed (fn [i f]
#(do (println "Waiting")
(a/<!! (a/timeout (* 1000 i sec)))
(f))) fns)]
(a/pipeline-blocking concurrent
output-chan
(map (fn [f] (f)))
(a/to-chan timedout-coll))
(a/<!! (a/into [] output-chan))))
If your objective is to work around the rate limiter, you can consider implementing it in the async channel. Below is one sample implementation - the function takes a channel, throttled its input with a token based limiter and pipe it to an output channel.
(require '[clojure.core.async :as async])
(defn rate-limiting-ch [input xf rate]
(let [tokens (numerator rate)
period (denominator rate)
ans (async/chan tokens xf)
next (fn [] (+ period (System/currentTimeMillis)))]
(async/go-loop [c tokens
t (next)]
(if (zero? c)
(do
(async/<! (async/timeout (- t (System/currentTimeMillis))))
(recur tokens (next)))
(when-let [x (async/<! input)]
(async/>! ans x)
(recur (dec c) t))))
ans))
And here is a sample usage:
(let [start (System/currentTimeMillis)
input (async/to-chan (range 10))
output (rate-limiting-ch input
;; simulate an api call with roundtrip time of ~300ms
(map #(let [wait (rand-int 300)
ans {:time (- (System/currentTimeMillis) start)
:wait wait
:input %}]
(Thread/sleep wait)
ans))
;; rate limited to 2 calls per 1000ms
2/1000)]
;; consume the output
(async/go-loop []
(when-let [x (async/<! output)]
(println x)
(recur))))
Output:
{:time 4, :wait 63, :input 0}
{:time 68, :wait 160, :input 1}
{:time 1003, :wait 74, :input 2}
{:time 1079, :wait 151, :input 3}
{:time 2003, :wait 165, :input 4}
{:time 2169, :wait 182, :input 5}
{:time 3003, :wait 5, :input 6}
{:time 3009, :wait 18, :input 7}
{:time 4007, :wait 138, :input 8}
{:time 4149, :wait 229, :input 9}

What would be the functional / clojure way of transforming a sequence with changing state?

The problem context relates to stock trading. I'm trying to update the holdings for a particular stock, when a sale is made. Simplified excerpt
;; #holdings - an atom
{ "STOCK1" {:trades [Trade#{:id 100 :qty 50}, Trade#{ :id 140 :qty 50}]}
"STOCK2" ... }
Now given a sale trade of Trade{:id 200 :stock "STOCK1", :qty 75}, I'm expecting the holdings to reflect
{ "STOCK1" {:trades [Trade#{:id 100 :qty 0}, Trade#{ :id 140 :qty 25}]} }
;; or better drop the records with zero qty.
{ "STOCK1" {:trades [Trade#{ :id 140 :qty 25}]} }
The functional answer eludes me.. All I can see is a doseq loop with atoms to hold state (like sale-qty which may be satisfied by 1 or n trades) - but it feels like C in Clojure.
Is there a more clojure-aligned solution to this? Map doesnt look like a fit because every record processing needs to update an external state (pending sale-qty 75 -> 25 -> 0)
Disclaimer: Clojure Newbie, who wants to learn.
(require '[com.rpl.specter :as s])
(let [stocks {"STOCK1" {:trades [{:trade/id 100 :trade/qty 50}, {:trade/id 140 :trade/qty 50}]}}
sale-trade {:trade/id 200 :trade/stock "STOCK1" :trade/qty 75}
trade-path [(s/keypath (:trade/stock sale-trade) :trades) s/ALL]
qty-path (conj trade-path :trade/qty)
[new-qty _] (reduce (fn [[new-amounts leftover] v]
(let [due-amount (min v leftover)]
[(conj new-amounts (- v due-amount)) (- leftover due-amount)]))
[[] (:trade/qty sale-trade)]
(s/select qty-path stocks))]
(->> stocks
(s/setval (s/subselect qty-path) new-qty)
(s/setval [trade-path #(zero? (:trade/qty %))] s/NONE)))
=> {"STOCK1" {:trades [#:trade{:id 140, :qty 25}]}}
Whenever you want to go over a sequence/collection in Clojure, while passing some additional state around think of reduce Reduce is like a Swiss army knife, for example map and filter can both be implemented with reduce. But how can you store multiple states in a reducing function? You simply use a map as the accumulator.
Let me distill your problem a bit. Let's create a function that only deals with one problem.
(defn substract-from
"Given a seq of numbers `values`, substract the number `value` from each number
in `values` until whole `value` is substracted. Returns a map with 2 keys, :result contains
a vector of substracted values and :rem holds a remainder."
[values value]
(reduce (fn [{:keys [rem] :as result} n]
(if (zero? rem)
(update result :result conj n)
(let [sub (min rem n)
res (- n sub)
rem (Math/abs (- sub rem))]
(-> result
(update :result conj res)
(assoc :rem rem)))))
{:rem value :result []}
values))
;; when value is smaller than the sum of all values, remainder is 0
(substract-from [100 200 300 400] 500)
;; => {:rem 0, :result [0 0 100 400]}
;; when value is larger than the sum of all values, remainder is > 0
(substract-from [100 200 300 400] 1200)
;; => {:rem 200, :result [0 0 0 0]}
Now we can use this function to sell stocks. Note that map can accept multiple collections/sequences as arguments.
(def stocks
(atom { "STOCK1" {:trades [{:id 100 :qty 50} { :id 140 :qty 50}]}}))
(defn sell [stocks {:keys [id stock qty]}]
(let [trades (get-in stocks [stock :trades])
qtys (map :qty trades)
new-qtys (:result (substract-from qtys qty))]
(map (fn [trade qty]
(assoc trade :qty qty))
trades
new-qtys)))
(sell #stocks {:id 300 :qty 75 :stock "STOCK1"})
;; => ({:id 100, :qty 0} {:id 140, :qty 25})
i would probably start with finding out which part of essential functionality is absent from the core library. In your case it is the function to map over the collection while keeping some changing state.
It could look this way:
(defn map-state [f state data]
(when-let [[x & xs] (seq data)]
(lazy-seq
(let [[new-state new-x] (f state x)]
(cons new-x (map-state f new-state xs))))))
small example of how it could work in context like yours:
(def running-subtract (partial map-state
#(let [qty (min %1 %2)]
[(- %1 qty) (- %2 qty)])))
#'user/running-subtract
user> (running-subtract 10 (range 7))
;;=> (0 0 0 0 0 5 6)
so, you can use it to subtract the state from your trades:
(defn running-decrease-trades [trades amount]
(map-state (fn [amount trade]
(let [sub (min (:qty trade) amount)]
[(- amount sub) (update trade :qty - sub)]))
amount
trades))
and transforming your data with this function would be as easy as the following:
(defn handle-trade [data {:keys [stock qty]}]
(update-in data [stock :trades] running-decrease-trades qty))
user> (handle-trade
{"STOCK1" {:trades [{:id 100, :qty 50} {:id 140, :qty 50}]}}
{:stock "STOCK1" :qty 75})
{"STOCK1" {:trades ({:id 100, :qty 0} {:id 140, :qty 25})}}
Although i like specter very much, i would say it is an overkill for this one.
Unlike imperative programming, where you often modify values in place, in functional programming you instead create new values that contain the modifications. So you will have to create a new version of your map (using update-in) that contains a modified vector with your trades. Something like this:
(def conj-positive-trade ((filter (comp pos? :qty)) conj))
(defn sell [trades sale]
(update-in trades
[(:stock sale) :trades]
#(first
(reduce (fn [[dst remaining] {:keys [qty id]}]
(let [diff (- qty remaining)]
[(conj-positive-trade dst {:id id :qty diff})
(max 0 (- diff))]))
[[] (:qty sale)]
%))))
Here, conj-positive-trade is a function that only conjoins positive trades to a vector.
Here is how to use the sell function:
(sell {"STOCK1" {:trades [{:id 100 :qty 50} {:id 140 :qty 50} {:id 150 :qty 70}]}}
{:id 200 :stock "STOCK1", :qty 75})
;; => {"STOCK1" {:trades [{:id 140, :qty 25} {:id 150, :qty 70}]}}
As an alternative solution that wouldn't use specter (which is great, but requires buy-in). I would keep two atoms, one that is a raw listing of all trades (a vector of maps that you just conj to, so for instance {:trade-id 1 :name "AAPL" :price 100 :qty 20}]), and another that is a map of maps indexed by stock name grouped-result. You'd go from one to the other by group-by or filter so if you added a trade in "AAPL" you can update the quantity as such:
(swap! grouped-result update-in ["AAPL"] (-> #listing (filter #(= (:name %) "AAPL")) (map :qty) (reduce +)))
When it comes to the trade-id you keep it's a bit more complicated as when you factor in PnL there can be FIFO or LIFO considerations - but again you can use reductions or reduced to stop where you want.

How is it possible to sort nested map by keys?

This
{0 {:data {7 2, 0 1, 3 4}}, 1 {:data {2 3, 1 1, 0 0}}}
should be sorted like this
{0 {:data {0 1, 3 4, 7 2}}, 1 {:data {0 0, 1 1, 2 3}}}
You can use a sorted map e.g.
(defn- sort-map [m]
(into (sorted-map) m))
(defn sort-data [m]
(->> m
(map (fn [[k v]] [k (update v :data sort-map)]))
(into {})))
then
(sort-data {0 {:data {7 2, 0 1, 3 4}}, 1 {:data {2 3, 1 1, 0 0}}})
here I go, promoting specter again! (as if it needs any promotion):
(require '[com.rpl.specter :refer [transform MAP-VALS]])
(transform [MAP-VALS :data] #(into (sorted-map) %) data)
;;=> {0 {:data {0 1, 3 4, 7 2}}, 1 {:data {0 0, 1 1, 2 3}}}
I have a function unlazy that I use for converting data into a canonical form. It recursively walks any data structure, converting all sequential types into vectors, and all maps/sets into sorted versions. It also converts native Java types into the Clojure equivalent:
(defn unlazy
"Converts a lazy collection to a concrete (eager) collection of the same type."
[coll]
(let [unlazy-item (fn [item]
(cond
(sequential? item) (vec item)
#?#(:clj [ (map? item) (into (sorted-map-generic) item)
(set? item) (into (sorted-set-generic) item) ]
:cljs [ (map? item) (into (sorted-map) item) ; #todo => (sorted-map-generic)
(set? item) (into (sorted-set) item) ; #todo => (sorted-map-generic)
] )
#?#(:clj [
(instance? java.io.InputStream item) (slurp item) ; #todo need test
(instance? java.util.List item) (vec item) ; #todo need test
(instance? java.util.Map item) (into {} item) ; #todo need test
(instance? java.lang.Iterable item) (into [] item) ; #todo need test
])
:else item))
result (walk/prewalk unlazy-item coll) ]
result))
For a simple case, it could be reduced like so:
(ns demo.core
(:require [clojure.walk :as walk]))
(defn walk-sorted
[coll]
(let [sort-item (fn [item]
(cond
(map? item) (into (sorted-map) item)
(set? item) (into (sorted-set) item) ]
:else item)) ]
(walk/prewalk sort-item coll))) ; or postwalk
Please note that sorted maps/sets are only useful for printing in a nice format (to make reading easier). Otherwise, it won't affect the way your program runs.
For advanced purposes, you may wish to explore the clojure.data.avl library and the API docs.

Combining Two clojure.walk/postwalk Calls Into One

I have the following data:
28 (def example {"1ce9b863-5681-4660-85e7-fbd0cc184aed"
29 {"58825b50-23bc-4204-8f8d-c9a9d3ac8beb" {},
30 "4b1763f9-8380-4507-9a8f-5c86878e49a9" {},
31 "160f34ac-68b9-4c8e-930b-1ab6df895df4" {}},
32 "6378daf6-3b7f-4cf4-8156-a50cf5f7b6ef"
33 {"669fe949-057f-43c0-af7b-ff39594a183d" {},
34 "73d2a203-e3c1-4d2f-aaf8-a9f2e870792b" {},
35 "8c9c57a0-d20d-4474-9afb-c9d17df83a91" {},
36 "94bf72cb-01cd-4430-b669-b2e954b5639b"
37 {"ba96a425-a3f0-4ce5-8c19-6ea9add14013" {},
38 "1ceff8fe-a0a8-46ad-81a8-d13fb837aaf6" {}}}})
39
40 (def titles (list {:id "58825b50-23bc-4204-8f8d-c9a9d3ac8beb", :title "Low"}
41 {:id "4b1763f9-8380-4507-9a8f-5c86878e49a9", :title "Medium"}
42 {:id "160f34ac-68b9-4c8e-930b-1ab6df895df4", :title "High"}
43 {:id "1ce9b863-5681-4660-85e7-fbd0cc184aed", :title "Priority"}
44 {:id "1ceff8fe-a0a8-46ad-81a8-d13fb837aaf6", :title "Drafting"}
45 {:id "ba96a425-a3f0-4ce5-8c19-6ea9add14013", :title "Brainstorm"}
46 {:id "94bf72cb-01cd-4430-b669-b2e954b5639b", :title "Planning"}
47 {:id "8c9c57a0-d20d-4474-9afb-c9d17df83a91", :title "Testing"}
48 {:id "73d2a203-e3c1-4d2f-aaf8-a9f2e870792b", :title "Implementation"}
49 {:id "669fe949-057f-43c0-af7b-ff39594a183d", :title "Completed"}
50 {:id "6378daf6-3b7f-4cf4-8156-a50cf5f7b6ef", :title "Status"}))
I am attempting to turn this data into a nested list structure using sablono/hiccup style syntax. Currently I have the following working solution:
52 (defn id->title [x]
53 (let [tuples (map (fn [x] [(:id x) (:title x)]) titles)]
54 (first (for [[k t] tuples :when (= k x)] t))))
57 (->> example
58 (clojure.walk/postwalk
59 (fn [x] (if-not (string? x)
60 (vec x) x)))
61 (clojure.walk/postwalk
62 (fn [x] (if (vector? x)
63 (if (vector? (first x))
64 (vec (cons :ul x))
65 (vec (cons :li x)))
66 (id->title x)))))
This results in:
[:ul
[:li "Priority"
[:ul
[:li "Low" [:li]]
[:li "Medium" [:li]]
[:li "High" [:li]]]]
[:li "Status"
[:ul
[:li "Completed" [:li]]
[:li "Implementation" [:li]]
[:li "Testing" [:li]]
[:li "Planning"
[:ul
[:li "Brainstorm" [:li]]
[:li "Drafting" [:li]]]]]]]
How can I simplify this to use one walk? I am also considering replacing titles with a map for an efficient lookup (I am pulling all of this data from a Neo4j).
Given the data you provided, you could try something like the following:
(defn id->title [x]
(->> titles (filter #(= (:id %) x)) first :title))
(defn format-data [structure]
(clojure.walk/postwalk
(fn [x]
(if (map? x)
(into [:ul] (map (fn [[k v]]
(if (= v [:ul])
[:li (id->title k)]
[:li (id->title k) v]))
(seq x)))
x))
structure))
During the postwalk, this will turn each map into a vector representing an unordered list (even the empty maps) and each key-value pair into a vector representing a list item. The (if (= v [:ul]) ...) ensures that the empty unordered lists are removed from the final structure.
Running (pprint (format-data example)) gives the following results:
[:ul
[:li "Priority" [:ul [:li "Low"] [:li "Medium"] [:li "High"]]]
[:li
"Status"
[:ul
[:li "Completed"]
[:li "Implementation"]
[:li "Testing"]
[:li "Planning" [:ul [:li "Brainstorm"] [:li "Drafting"]]]]]]

group-by with reduce in clojure

I want to aggregate large dataset to get something like
SELECT SUM(`profit`) as `profit`, `month` FROM `t` GROUP BY `month`
So, i modified clojure's group-by function like so
(defn group-reduce [f red coll]
(persistent!
(reduce
(fn [ret x]
(let [k (f x)]
(assoc! ret k (red (get ret k) x))))
(transient {}) coll)))
And here is usage:
(group-reduce :month (fn [s x]
(if s
(assoc s :profit (+ (:profit s) (:profit x)))
x))
[{:month 10 :profit 12}
{:month 10 :profit 15}
{:month 12 :profit 1}])
#_=> {10 {:profit 27, :month 10}, 12 {:profit 1, :month 12}}
It works, but maybe there is another way to do this, using clojure standard library?
Closest in the core is merge-with:
(def t [{:month 10 :profit 12}
{:month 10 :profit 15}
{:month 12 :profit 1}])
(apply merge-with + (for [x t] {(:month x) (:profit x)}))
;=> {12 1, 10 27}
Some examples:
user=> (def groups (group-by :month [{:month 10 :profit 12}
#_=> {:month 10 :profit 15}
#_=> {:month 12 :profit 1}])
{10 [{:profit 12, :month 10} {:profit 15, :month 10}], 12 [{:profit 1, :month 12}]}
user=> (for [[k v] groups] {:month k :sum-profit (apply + (map :profit v))})
({:month 10, :sum-profit 27} {:month 12, :sum-profit 1})
user=> (into {} (for [[k v] groups] [k (apply + (map :profit v))]))
{10 27, 12 1}