John Doe
John Doe

Reputation: 281

How to map based on multiple lists with arbitrary elements?

I have the following model:

case class Car(brand: String, year: Int, model: String, ownerId: String)
case class Person(firstName: String, lastName: String, id: String)
case class House(address: String, size: Int, ownerId: String)
case class Info(id: String, lastName: String, carModel: String, address: String)

I want to build a List[Info] based on the following lists:

val personL: List[Person] = List(Person("John", "Doe", "1"), Person("Jane", "Doe", "2"))
val carL: List[Car] = List(Car("Mercedes", 1999, "G", "1"), Car("Tesla", 2016, "S", "4"), Car("VW", 2015, "Golf", "2"))
val houseL: List[House] = List(House("Str. 1", 1000, "2"), House("Bvl. 3", 150, "8"))

The info should be gathered based on the personL, for example:

val info = personL.map { p =>
      val car = carL.find(_.ownerId.equals(p.id))
      val house = houseL.find(_.ownerId.equals(p.id))
      val carModel = car.map(_.model)
      val address = house.map(_.address)
      Info(p.id, p.lastName, carModel.getOrElse(""), address.getOrElse(""))
      }

Result:

info: List[Info] = List(Info(1,Doe,G,), Info(2,Doe,Golf,Str. 1))

Now I am wondering if there's an expression which is more concise than my map construct which solves exactly my problem.

Upvotes: 1

Views: 393

Answers (3)

makeyourrules
makeyourrules

Reputation: 63

May be converting the individual lists in hashmaps with a map function and look up by key instead of iterating all those lists for every element of person might help?

Upvotes: 0

Andrei T.
Andrei T.

Reputation: 2480

I would say use for comprehensions. If you need exactly that result which in that case would resemble a left join then the for comprehension is still ugly:

for {
  person  <- persons
  model   <- cars.find(_.ownerId == person.id).map(_.model).orElse(Some("")).toList
  address <- houses.find(_.ownerId == person.id).map(_.address).orElse(Some("")).toList
} yield Info(person.id, person.lastName, model, address)

Note that you can remove the .toList call in this exceptional case as the two Option generators appear after the collection generators.

If you can sacrifice the default model / address values then it looks simple enough:

for {
  person <- persons
  car    <- cars if car.ownerId == person.id
  house  <- houses if house.ownerId == person.id
} yield Info(person.id, person.lastName, car.model, car.address)

Hope that helps.

Upvotes: 0

akuiper
akuiper

Reputation: 214957

Here is one option by building the maps from ownerid to model and address firstly, and then look up the info while looping through the person List:

val carMap = carL.map(car => car.ownerId -> car.model).toMap
// carMap: scala.collection.immutable.Map[String,String] = Map(1 -> G, 4 -> S, 2 -> Golf)

val addrMap = houseL.map(house => house.ownerId -> house.address).toMap
// addrMap: scala.collection.immutable.Map[String,String] = Map(2 -> Str. 1, 8 -> Bvl. 3)

personL.map(p => Info(p.id, p.lastName, carMap.getOrElse(p.id, ""), addrMap.getOrElse(p.id, "")))
// res3: List[Info] = List(Info(1,Doe,G,), Info(2,Doe,Golf,Str. 1))

Upvotes: 2

Related Questions