16

this is a newbie question

I have the following code:

var total = 0L
docs.foreach(total += _.length)

in docs I have a collection of objects with the .length property

I'd like something like:

val total = docs.[someScalaMethod](0, (element, acum) => acum + element.length )

I mean, a method that iterates each element passing an accumulator variable...

The first zero I pass should be the initial value of the accumulator var..

How can it be achieved?

opensas
  • 60,462
  • 79
  • 252
  • 386
  • 1
    You might find http://stackoverflow.com/questions/2293592 helpful also, but given your question you seem to know exactly what you need, so you don't need a tutorial--you just need to know what the method is called. – Rex Kerr Dec 04 '11 at 14:02
  • I've finnally settled for val docsLength = if (docs.length == 0) 0 else docs.map(_.length).sum but all answers were really very helpful – opensas Dec 04 '11 at 17:44
  • 1
    `docs.map(_.length).sum` will do exactly the same thing; the if/else is the default behavior for `sum`. – Rex Kerr Dec 04 '11 at 17:56
  • yes, I tested it and you are right... – opensas Dec 04 '11 at 18:16

3 Answers3

25

This called a fold. It's almost exactly what you stated:

docs.foldLeft(0)((accum, element) => accum + element.length)

for the version that traverses the collection from left to right (usually preferable; right to left is foldRight, and 2.9 has a fold that can start anywhere, but has limitations on how it can transform the type).

Once you get used to this, there is a short-hand version of fold left, where the accumulator goes on the left (think of it being pushed from left to right through the list), and you use placeholders for the variable names since you only use them once each: (0 /: docs)(_ + _.length)

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • fold method does not exists for Array (well, that's the error I get) Had to use foldLeft, and also specify 0L an initial value, like this: docs.foldLeft(0L)( (acum, ele) => acum + ele.length ) – opensas Dec 04 '11 at 14:32
  • 2
    @opensas - You're using an older version of Scala, I guess; `fold` was introduced in 2.9, as I recall. Before that there were only left and right versions. – Rex Kerr Dec 04 '11 at 15:25
  • you are right, scala -version Scala code runner version 2.8.1.final -- Copyright 2002-2010, LAMP/EPFL – opensas Dec 04 '11 at 17:17
  • tried with 2.9.1.final, I get the following error: error: value fold is not a member of Array[this.DocumentationFile] val translatedLength = translated.fold(0L)( (acum, element) => acum + element.length ) – opensas Dec 04 '11 at 17:33
  • @opensas - My mistake. I forgot that `fold` can't change the return type in that way. – Rex Kerr Dec 04 '11 at 17:36
9
docs map { _.length } reduce { _ + _ }

or (the thx goes to Luigi Plinge)

docs.map(_.length).sum
agilesteel
  • 16,775
  • 6
  • 44
  • 55
  • 1
    Note that this fails if `docs` is empty, but is otherwise a decent strategy. – Rex Kerr Dec 04 '11 at 14:03
  • with scala 2.9.1, and the first choice, I get the following error: error: value reduce is not a member of Array[Long] val translatedLength = translated map { _.length } reduce { _ + _ } – opensas Dec 04 '11 at 17:35
  • 1
    @opensas - I'm pretty sure you have 2.8 again there. Works fine for me on 2.9.1. – Rex Kerr Dec 04 '11 at 21:43
  • You are right, rex, I tried it again and it works ok, thanks. – opensas Dec 05 '11 at 00:05
4

Here is a Scalaz version:

docs.foldMap(_.length)

This is equivalent to docs.map(_.length).sum but takes only one pass. Also, works with all monoids.

missingfaktor
  • 90,905
  • 62
  • 285
  • 365