ktii
ktii

Reputation: 59

Scala - conditionally sum elements in list using foldLeft

I am trying to solve a beginner problem where I have a class

class Invoice {
 var amount   = 0d
 var item   = ""
 var paid = false
 }

and given a list of invoices, I need to sum unpaid amounts using foldLeft and return it as a float. My attempt:

def toBePaid(as: List[Invoice]): Double = {
 var total = 0
 as.foldLeft(0.0)((as,total) => if (as.paid==false) total + as.amount)
 }

But it doesn't work. What am I doing wrong?

Ok, got it working based on given help:

def toBePaid(as: List[Invoice]): Double = {
 as.foldLeft(0.0)((total,as) => if (as.paid==false) total + as.amount else total + 0)
 }

Upvotes: 0

Views: 911

Answers (1)

GatorCSE
GatorCSE

Reputation: 158

There are a few simple mistakes here. First of all, you have the arguments backwards in your iteration function in foldleft. The signature is foldLeft[B](z: B)(op: (B, A) ⇒ B): B, so your example should be (total, as) => .... You are also shadowing the total variable, you declare it in the body of toBePaid, and again in the iteration function, so the reference to total in total + as.amount is ambiguous.

The bigger issue is that you are mixing mutable and immutable state. As a rule of thumb, most of the constructs in Scala are designed to be used with immutable state only (val, not var). In your example, there is no reason for the values "Invoice" class change, so they should all be declared as val, not var. But that is less important in this specific case than how you are calculating the total.

In toBePaid, you are setting up your loop like classic imperative looping: by creating a mutable variable initialized to 0, with the idea that you will mutate the value by adding as.amount to it each time. With foldLeft, you don't need to keep track of the sum outside the function, the fold handles it for you. What fold loosely says is "Given an initial state (0.0), and a method takes a state/element pair and calculates a new state, then return me the final state." You provided a function that takes the element (as) and state so far (total) to calculate a new state. Fold will take that resulting new state and feed it as the state in the next iteration. In short, you can remove the declaration var total = 0.

Another issue is that the iteration function must return a new state, (ie, a new total). In your case, if the if statement is true, you return the next state, but the else case fails to return anything. You need to have else ... return the next state in case the invoice is not paid. Alternatively, before the call to foldLeft, you can call filter to filter only the paid invoices, and then call foldLeft unconditionally.

Upvotes: 1

Related Questions