12

Starting with a list of objects containing two parameters notional and currency, how can I aggregate the total notional per currency?

Given:

case class Trade(name: String, amount: Int, currency: String)

val trades = List(
  Trade("T150310", 10000000, "GBP"),
  Trade("T150311", 10000000, "JPY"),
  Trade("T150312", 10000000, "USD"),
  Trade("T150313", 100, "JPY"),
  Trade("T150314", 1000, "GBP"),
  Trade("T150315", 10000, "USD")
)

How can I get:

Map(JPY -> 10000100, USD -> 10010000, GBP -> 10001000)
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
parkr
  • 3,188
  • 5
  • 35
  • 39

3 Answers3

17

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))
psp
  • 12,138
  • 1
  • 41
  • 51
  • And can you explain where the sum function is coming from? – oxbow_lakes Jun 22 '09 at 22:26
  • Yes, what is trunk now is what 2.8 will be. The sum method is defined on NumericTraversableOps - which is not a class you need to know anything about - but it basically adds methods to Traversable by way of implicit based on the presence of a Numeric[T], which itself defines "add" so sum can be generically defined. – psp Jun 23 '09 at 04:25
  • The last comment is a bit out of date, as `sum` is now defined on ` TraversableTemplate`. – Daniel C. Sobral Sep 02 '09 at 00:15
4

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].

oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
1

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:

  • groups elements based on their currency (group part of groupMapReduce)

  • maps grouped values to their amount (map part of groupMapReduce)

  • reduces 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(_+_))
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190