Mark Spangler
Mark Spangler

Reputation: 4856

Scala vals vs vars

I'm pretty new to Scala but I like to know what is the preferred way of solving this problem. Say I have a list of items and I want to know the total amount of the items that are checks. I could do something like so:

val total = items.filter(_.itemType == CHECK).map(._amount).sum

That would give me what I need, the sum of all checks in a immutable variable. But it does it with what seems like 3 iterations. Once to filter the checks, again to map the amounts and then the sum. Another way would be to do something like:

var total = new BigDecimal(0)
for (
    item <- items
    if item.itemType == CHECK
) total += item.amount

This gives me the same result but with 1 iteration and a mutable variable which seems fine too. But if I wanted to to extract more information, say the total number of checks, that would require more counters or mutable variables but I wouldn't have to iterate over the list again. Doesn't seem like the "functional" way of achieving what I need.

var numOfChecks = 0
var total = new BigDecimal(0)
items.foreach { item =>
    if (item.itemType == CHECK) {
        numOfChecks += 1
        total += item.amount
    }
}

So if you find yourself needing a bunch of counters or totals on a list is it preferred to keep mutable variables or not worry about it do something along the lines of:

val checks = items.filter(_.itemType == CHECK)
val total = checks.map(_.amount).sum
return (checks.size, total)

which seems easier to read and only uses vals

Upvotes: 4

Views: 972

Answers (3)

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297175

I find that speaking of doing "three iterations" is a bit misleading -- after all, each iteration does less work than a single iteration with everything. So it doesn't automatically follows that iterating three times will take longer than iterating once.

Creating temporary objects, now that is a concern, because you'll be hitting memory (even if cached), which isn't the case of the single iteration. In those cases, view will help, even though it adds more method calls to do the same work. Hopefully, JVM will optimize that away. See Moritz's answer for more information on views.

Upvotes: 7

Moritz
Moritz

Reputation: 14212

Another way of solving your problem in one iteration would be to use views or iterators:

items.iterator.filter(_.itemType == CHECK).map(._amount).sum

or

items.view.filter(_.itemType == CHECK).map(._amount).sum

This way the evaluation of the expression is delayed until the call of sum.

If your items are case classes you could also write it like this:

items.iterator collect { case Item(amount, CHECK) => amount } sum

Upvotes: 8

Vasil Remeniuk
Vasil Remeniuk

Reputation: 20619

You may use foldLeft for that:

(0 /: items) ((total, item) => 
   if(item.itemType == CHECK) 
      total + item.amount 
   else 
      total
)

The following code will return a tuple (number of checks -> sum of amounts):

((0, 0) /: items) ((total, item) => 
   if(item.itemType == CHECK) 
      (total._1 + 1, total._2 + item.amount) 
   else 
      total
)

Upvotes: 6

Related Questions