14

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?

soc
  • 27,983
  • 20
  • 111
  • 215
  • 1
    I guess `sumBy` (cf. `sortBy`) would be a reasonable extension for the collection library. – Raphael Feb 21 '11 at 11:58
  • That's what I was thinking too. Although I can't really decide between a) Overloading sum (many devs don't like overloading) b) sumBy (consistent with SortBy) and c) sumOf (nicer to read, like "totalAge is defined as persons sumOf age"). – soc Feb 21 '11 at 12:19
  • Ugh, library extension? Next we'll need `prodOf` and `maxOf` and `minOf`. And then those of us who like median or mean or entropy or (etc.) will feel left out. A good old `mapReduce` might be nice. But beyond that I think it's better for all involved to get used to folding and/or views. – Rex Kerr Feb 21 '11 at 18:20
  • @Rex Kerr: Uh? We already have `maxBy` and `minBy`! That's the point I am making here. – soc Feb 22 '11 at 14:48
  • @soc - Argh. Well, it's not in 2.8.1. And I think it's foolish to do for 2.9. – Rex Kerr Feb 22 '11 at 15:05
  • Why? Following these arguments, we could remove _everything_ except `filter`, `flatten` and `map`, because those are just some "convenenience" method. – soc Feb 22 '11 at 17:22

3 Answers3

13

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
shellholic
  • 5,974
  • 1
  • 20
  • 30
  • Well, `view` was exactly the thing i don't wanted to use. :-) – soc Feb 21 '11 at 12:14
  • 2
    May I ask why you exclude `view`? – shellholic Feb 21 '11 at 12:20
  • I wondered if I did miss a method which fills the gap between `sum` and `foldLeft`. – soc Feb 21 '11 at 12:38
  • 1
    You didn't miss anything. `sum` is more or less your `foldLeft`: `def sum[B >: A](implicit num: Numeric[B]): B = foldLeft(num.zero)(num.plus)` (from Scala source). I don't know a to make something in-between without adding indirection. – shellholic Feb 21 '11 at 12:44
  • Looks like someone just filed a ticket about it: https://lampsvn.epfl.ch/trac/scala/ticket/4276 – soc Feb 21 '11 at 13:00
11

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) 
}
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
0

In Scala 3 you can create the following extension:

extension [T](iterable: Iterable[T])
  def sumBy[U](f: T => U)(using n: Numeric[U]): U = iterable.foldLeft(n.zero)((acc, elem) => n.plus(acc, f(elem)))
Simão Martins
  • 1,210
  • 11
  • 22