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)

3 Answers 3

16

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))
Sign up to request clarification or add additional context in comments.

3 Comments

And can you explain where the sum function is coming from?
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.
The last comment is a bit out of date, as sum is now defined on ` TraversableTemplate`.
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].

Comments

2

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(_+_))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.