Reputation: 2649
I have something like this
case class Job(workId: Int, users: List[String])
val jobs = IndexedSeq(Job(1, List("a", "b")), Job(2, List("b", "c")), Job(3, List("a", "c" )), Job(4, List("d", "b")))
I want to convert this to something like:
Map(c -> Vector(2, 3), a -> Vector(1, 3), d -> Vector(4), b -> Vector(1, 2, 4))
I basically want to maintain the order of Job.workId in the original sequence. So since the Job with workId 1 comes before the job with workId 3, a's entry in the map has JobId 1 before JobId 3.
I couldn't find a straight forward way of doing this. Right now I have:
((for (job <- jobs;
user <- job.users)
yield { (user, job.work) }) groupBy { tuple => tuple._1 }) map { tuple => (tuple._1 -> (tuple._2 map { _._2 })) }
This first creates:
Map(c -> Vector((c,2), (c,3)), a -> Vector((a,1), (a,3)), d -> Vector((d,4)), b -> Vector((b,1), (b,2), (b,4)))
and then transforms it to:
Map(c -> Vector(2, 3), a -> Vector(1, 3), d -> Vector(4), b -> Vector(1, 2, 4))
This seems rather verbose. I am wondering if there is an easier way to do this while preserving the order. Also I don't like that it needs to iterate over the initial Sequence multiple times.
I have another lengthier solution:
val mapping = scala.collection.mutable.Map[String, IndexedSeq[Int]]()
for (job <- jobs;
user <- job.users)
yield{
if (mapping.contains(user)) {
val entry = mapping(user)
mapping.put(user, entry :+ job.work)
} else {
mapping += user -> mutable.IndexedSeq(job.work)
}
}
mapping now is:
Map(c -> ArrayBuffer(2, 3), a -> ArrayBuffer(1, 3), d -> ArrayBuffer(4), b -> ArrayBuffer(1, 2, 4))
This shares the initial for comprehensions but does not need the extra iterations that come from using groupBy and then map.
Is there a more idiomatic and concise way of doing this with the standard collection methods?
Upvotes: 3
Views: 897
Reputation: 5069
As with just about all questions with List processing, this is solvable with a fold!
(for {
job <- jobs.view;
user <- job.users
} yield (job, user)).foldLeft (Map[String, Vector[Int]]()) { case (acc, (a,b)) =>
acc + (b -> (acc.getOrElse(b, Vector()) :+ a.workId))
}
Unfortunately, Scala's type inferencer is not capable of determining the type of the initial 'Map', so you have to specify it explicitly.
Using the 'view' method on the initial collection makes this lazy, and will only perform a single pass through the initial list.
Upvotes: 1