jvermeir (5) [Avatar] Offline
#1
The example on page 124 (meap-v5) returns the correct answer only because of the order of Employee's in the list. If the order is changed, e.g. by moving Employee("Joan", 41, "Finance") to the top of the list, the code returns Joan in stead of Mary.

I've changed the solution, introducing a dummy employee as the seed for foldLeft:

employees
.filter(_.department == "IT")
.foldLeft(Employee("dummy", 0, "IT")) { (e1, e2) => if (e1.age>e2.age) e1 else e2}


This works, but I was wondering if there is a generic way to solve what looks to me like an initialization problem with foldLeft in general?
Mark Elston (129) [Avatar] Offline
#2
Are you sure you copied the code correctly. "Joan" should never appear in the output since the filter operation (on _.department == "IT") should remove her record.

BTW, there is a second filter in that listing that is unnecessary.
jvermeir (5) [Avatar] Offline
#3
Hi Mark, thanks, but yes I did copy the code correctly. The problem is not in the filter but in the seed to foldLeft. The filter will select all employees in IT, so far the code works. But then the seed to foldLeft is taken from all employees.
In the example the list of employees starts with Bob, age 20 so the first call to foldLeft will compare Bob with Pete and select Pete as the new value. The next foldLeft will then compare Pete with Mary and return Mary as the correct answer.
If you reorder the employees in the list so it starts with Joan, like this:
val employees = List(
  Employee("Joan", 41, "Finance"),
   ...
    Employee("Mary", 32, "IT")
)

the seed to foldLeft will be Joan and consequently she is returned as the result.
So even though the filter is ok, the call to foldLeft reintroduces a non-IT employee.

For completeness, I'm copying my version below:
case class Employee(name:String, age:Int, department:String)

val employees = List(
  Employee("Joan", 41, "Finance"),
    Employee("Bob", 20, "Sales"),
    Employee("Mary", 32, "Finance"),
    Employee("Pete", 28, "IT"),
    Employee("Ken", 25, "Sales"),
    Employee("Tom", 30, "Sales"),
    Employee("Mary", 32, "IT")
)

employees.filter(_.department == "IT").foldLeft(Employee("dummy", 0, "IT")) { (e1, e2) => if (e1.age>e2.age) e1 else e2}
employees.filter(_.department == "IT").foldLeft(employees.head) { (e1, e2) => if (e1.age>e2.age) e1 else e2}


and the output I'm seeing is this:
defined class Employee
employees: List[Employee] = List(Employee(Joan,41,Finance), Employee(Bob,20,Sales), Employee(Mary,32,Finance), Employee(Pete,28,IT), Employee(Ken,25,Sales), Employee(Tom,30,Sales), Employee(Mary,32,IT))
res0: Employee = Employee(Mary,32,IT)
res1: Employee = Employee(Joan,41,Finance)
Mark Elston (129) [Avatar] Offline
#4
Hah. You're right. I didn't pay attention to the details of the change you recommended and I completely missed the seed to foldLeft. The "dummy" entry is the key. Good catch.

What you would really like is a way to take the first element out of the filter and use that as the seed. The only way I can think of to do that is to break the flow into two parts. Something like this:

val itEmployees = employees.filter(_.department == "IT")

val oldest = itEmployees.foldLeft(itEmployees.head) { 
    (e1, e2) if (e1.age > e2.age) e1 else e2
}
Mark Elston (129) [Avatar] Offline
#5
Alternatively, you can get the seed outside the flow and use the flow as is:

// This returns an Option<Employee>, Some(emp) if found, None otherwise
val firstIT = employees.find( _.department = "IT") 

// Define 'flow' to be the original flow
// 'emps' is the list of employees
// 'seed ' is the seed to the foldLeft
def flow emps seed = 
    emps.filter(_.department == "IT")
        .foldLeft(seed) { 
            (e1, e2) => if (e1.age > e2.age) e1 else e2
        }

// Now 'oldest' is an Option<Employee>
val oldest = seed match {
    Some e => Some (flow employees firstIT)
    None => None
}


This also handles the case where there are no Employees from IT in the list.