Starting with a list of objects containing two parameters notional and currency, how can I aggregate the total notional per currency?
Given:
case class T
I wrote a simple group-by operation (actually a Groupable
trait
with an implicit conversion from an Iterable
) which would allow you to group your trades by their currency
:
trait Groupable[V] extends Iterable[V] {
def groupBy(f: V => K): MultiMap[K, V] = {
val m = new mutable.HashMap[K, Set[V]] with mutable.MultiMap[K, V]
foreach { v => m add (f(v), v) } //add is defined in MultiMap
m
}
}
implicit def it2groupable(it: Iterable[V]): Groupable[V] = new Groupable[V] {
def elements = it.elements
}
So Groupable
is simply providing a way to extract a key from each item in an Iterable
and then grouping all such items which have the same key. So, in your case:
//mm is a MultiMap[Currency, Trade]
val mm = trades groupBy { _.currency }
You can now do a quite simple mapElements
(mm
is a Map
) and a foldLeft
(or /:
- well worth understanding the foldLeft
operator as it enables extremely concise aggregations over collections) to get the sum:
val sums: Map[Currency, Int] = mm mapElements { ts =>
(0 /: ts) { (sum,t) => sum + t.notional }
}
Apologies if I've made some mistakes in that last line. ts
are the values of mm
, which are (of course) Iterable[Trade]
.
Starting Scala 2.13
, most collections are provided with the groupMapReduce method which is (as its name suggests) an equivalent (more efficient) of a groupBy
followed by mapValues
and a reduce step:
trades.groupMapReduce(_.currency)(_.amount)(_ + _)
// immutable.Map[String,Int] = Map(JPY -> 10000100, USD -> 10010000, GBP -> 10001000)
This:
group
s elements based on their currency (group part of groupMapReduce)
map
s grouped values to their amount (map part of groupMapReduce)
reduce
s values (_ + _
) by summing them (reduce part of groupMapReduce).
This is an equivalent version performed in one pass through the List of:
trades.groupBy(_.currency).mapValues(_.map(_.amount).reduce(_+_))
If you use trunk the machinery is already there. groupBy is defined on Traversable and sum can be applied directly to the list, you don't have to write a fold.
scala> trades groupBy (_.currency) map { case (k,v) => k -> (v map (_.amount) sum) }
res1: Iterable[(String, Int)] = List((GBP,10001000), (JPY,10000100), (USD,10010000))