0

Given,

case class User(name: String, roles: List[String])
val users: List[User] = ...

I'd like to calculate a map --

val roleToUsers: Map[String, List[User]] = ???

I could do the concise:

(for (user <- users; role <- user.roles) yield (role, user)).groupBy(_._1).mapValues(_.map(_._2))

But the underscores make it a bit too cryptic for my liking. Is there a neater way to do this that doesn't make it much more verbose?

Edit: List[Role] -> List[User]

Matt R
  • 9,892
  • 10
  • 50
  • 83
  • 1
    Did you mean `Map[String, List[User]]`? That's what your example results in. – Ben James Oct 19 '11 at 09:56
  • 1
    Try looking at this question for alternatives: [Reverse transpose a one to many map in scala][1] [1]: http://stackoverflow.com/questions/5498833/reverse-transpose-a-one-to-many-map-in-scala – Benedict Oct 19 '11 at 09:56
  • You don't have to use anonymous functions and underscores if you don't want to. – Emil Sit Oct 19 '11 at 15:00
  • 1
    I just tried making a version with everything fully named, as suggested by Emil, and... it was even harder to read. :( (And usually I'm one for verboseness.) Maybe break the statement into multiple lines, and add a couple short inline comments? – Rodney Gitzel Oct 19 '11 at 18:27

2 Answers2

1

You can use pattern matching with case to make the naming of parameters more explicit:

(for { user <- users; role <- user.roles } yield (role, user)) groupBy
     { case (role, _) => role } mapValues
        { roleUser => roleUser map { case (_, user) => user} }

It's not much longer and a bit clearer, especially if split over a few lines. In the above the _ are only used to mean "don't care", but you could also name everything to avoid _ altogether:

(for { user <- users; role <- user.roles } yield (role, user)) groupBy
     { case (role, user) => role } mapValues
        { roleUser => roleUser map { case (role, user) => user} }
ebruchez
  • 7,760
  • 6
  • 29
  • 41
1

Maybe this:

val lst = for {
  user <- users
  role <- user.roles
} yield (role, users collect {
  case user if user.roles contains role => user.name
})
val map = lst.toMap

Or, without for-comprehensions and with a minor optimization

users.flatMap(_.roles).distinct.map(role => 
  (role, users collect { 
    case user if user.roles contains role => user.name })).toMap
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681