tags:

views:

120

answers:

4

Is there a better way of doing this:

val totalScore = set.foldLeft(0)( _ + score(_) )

or this:

val totalScore = set.map(score(_)).sum

I think it's quite a common operation so was expecting something sleeker like:

val totalScore = set.sum( score(_) )
+1  A: 

Simpler:

scala> val is1 = Set(1, 4, 9, 16)
is1: scala.collection.immutable.Set[Int] = Set(1, 4, 9, 16)
scala> is1.reduceLeft(_ + _)
res0: Int = 30

With your score method:

scoreSet.reduceLeft(_ + score(_))

Warning, though, this fails is the collection being reduced is empty while fold does not:

scala> val is0 = Set[Int]()
is0: scala.collection.immutable.Set[Int] = Set()

scala> is0.foldLeft(0)(_ + _)
res1: Int = 0
Randall Schulz
This won't work. The type of the collection is different than the type of the result (which is the reason for the call to `score`), so `reduceLeft` is not an option.
Daniel
+1  A: 

Alternately, the Seq#sum overload that takes an implicit conversion to Numeric could be used if the type in the collection to be scored / summed does not itself have an addition operator. However, because it's an implicit conversion parameter, it won't be applied unless required to make the reduce closure type-check.

Randall Schulz
+4  A: 

Seq.sum does not take a function which could be used to score the sum. You could define an implicit conversion which "pimps" Traversable:

implicit def traversableWithSum[A](t: Traversable[A])(implicit m: Numeric[A]) = new {
  def sumWith(f: A => A) = t.foldLeft(m.zero)((a, b) => m.plus(a, f(b)))
}

def score(i: Int) = i + 1

val s = Set(1, 2, 3)

val totalScore = s.sumWith(score _)
println(totalScore)
=> 9

Please note that the Numeric trait only exists in Scala 2.8.

Michel Krämer
Michel, I like your solution and I'd thought I would make it a bit more generic by allowing score to work on an object, like "Game" and return a Numeric:implicit def traversableWithSum[A](t: Traversable[A]) = new { def sumWith[B](f: A => B)(implicit m: Numeric[B]): B = t.foldLeft(m.zero)((a, b) => m.plus(a, f(b)))}
Eric
+7  A: 

Well, there are alternative ways to write it:

val totalScore = set.map(score(_)).sum
val totalScore = set.map(score).sum
val totalScore = set map score sum

The last one may require a semi-colon at the end if the next line doesn't start with a keyword.

Daniel
This seems like the simplest and most appropriate solution. I'd prefer to read this instead of having a function that includes doing the mapping.
ziggystar
A legitimate concern with the two step approach may be performance -- it creates a second list just to sum it. However, you can use a view to avoid this overhead: `set.view.map(score).sum`. Separating the concerns of mapping and summing avoids a blow-out of methods in the standard library. If you do it frequently, you can add `mapAndSum` to your own code.
retronym
Does anyone have a link to some documentation on views?
adam77