Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to sum fields of collection elements without mapping them first (like foldLeft/reduceLeft)?

Consider this class:

 case class Person(val firstName: String, val lastName: String, age: Int)
 val persons = Person("Jane", "Doe", 42) :: Person("John", "Doe", 45) :: 
               Person("Joe", "Doe", 43) :: Person("Doug", "Don", 65) :: 
               Person("Darius", "Don", 24) :: Person("Dora", "Don", 20) :: 
               Person("Dane", "Dons", 29) :: Nil

To get the sum of the age of all persons, I can write code like:

persons.foldLeft(0)(_ + _.age)

But if I want to use sum, I need to map the value first and the code looks like this:

persons.map(_.age).sum

How can I use the sum method without creating some intermediate collection?

(I know that such an "optimization" most probably doesn't have any real performance difference when not run in a tight loop and I also know about lazy views and so on.)

Is it possible to have code like

persons.sum(_.age)

doing what foldLeft/reduceLeft does?

like image 401
soc Avatar asked Feb 21 '11 09:02

soc


2 Answers

You answered is yourself. Just use view:

persons.view.map(_.age).sum

To convince yourself by examining the workflow:

persons.view.map { p =>
  println("invoking age")
  p.age
}.map { x =>
  println("modifing age")
  x + 0
}.sum

Vs:

persons.map { p =>
  println("invoking age")
  p.age
}.map { x =>
  println("modifing age")
  x + 0
}.sum
like image 141
shellholic Avatar answered Nov 02 '22 04:11

shellholic


The method sum in the library doesn't work this way, but you could write your own which does:

def mySum[T, Res](f: T => Res, seq: TraversableOnce[T])(implicit num: Numeric[Res]) = 
  seq.foldLeft(num.zero)((acc, b) => num.plus(acc, f(b)))

You could also add an implicit conversion so you can call it like seq.sum(f) instead of mySum(f, seq) (you may need a different name than sum to avoid conflicts):

case class SumTraversableOnce[T](val seq: TraversableOnce[T]) { 
  def sum[Res](f: T => Res)(implicit num: Numeric[Res]) = mySum(f, seq)(num) 
}

implicit def toSumTraversableOnce[T](seq: TraversableOnce[T]) = 
  SumTraversableOnce(seq)

or, since Scala 2.10,

implicit class SumTraversableOnce[T](val seq: TraversableOnce[T]) { 
  def sum[Res](f: T => Res)(implicit num: Numeric[Res]) = mySum(f, seq)(num) 
}
like image 31
Alexey Romanov Avatar answered Nov 02 '22 06:11

Alexey Romanov