views:

938

answers:

3

If I have a collection c of type T and there is a property p on T (of type P, say), what is the best way to do a map-by-extracting-key?

val c: Collection[T]
val m: Map[P, T]

One way is the following:

m = new HashMap[P, T]
c foreach { t => m add (t.getP, t) }

But now I need a mutable map. Is there a better way of doing this so that it's in 1 line and I end up with an immutable Map? (Obviously I could turn the above into a simple library utility, as I would in Java, but I suspect that in Scala there is no need)

+3  A: 

You can construct a Map with a variable number of tuples. So use the map method on the collection to convert it into a collection of tuples and then use the : _* trick to convert the result into a variable argument.

scala> val list = List("this", "maps", "string", "to", "length") map {s => (s, s.length)}
list: List[(java.lang.String, Int)] = List((this,4), (maps,4), (string,6), (to,2), (length,6))

scala> val list = List("this", "is", "a", "bunch", "of", "strings")
list: List[java.lang.String] = List(this, is, a, bunch, of, strings)

scala> val string2Length = Map(list map {s => (s, s.length)} : _*)
string2Length: scala.collection.immutable.Map[java.lang.String,Int] = Map(strings -> 7, of -> 2, bunch -> 5, a -> 1, is -> 2, this -> 4)
James Iry
I've been reading about Scala for >2 weeks and working through examples and not once had I seen this ": _ *" notation! Thanks very much for your help
oxbow_lakes
+4  A: 

In addition to @James Iry's solution, it is also possible to accomplish this using a fold. I suspect that this solution is slightly faster than the tuple method (fewer garbage objects are created):

val list = List("this", "maps", "string", "to", "length")
val map = list.foldLeft(Map[String, Int]()) { (m, s) => m(s) = s.length }
Daniel Spiewak
I will try this out (I'm sure it works :-). What is going on with the "(m,s)=>m(s) = s.length" function? I have seen the typical foldLeft example with a sum and a function "_ + _"; this is much more confusing! The function seems to assume that I already have a tuple (m,s), which I don't really get
oxbow_lakes
*Is* this right? According to the scaladoc of foldLeft: "foldLeft [B](z : B)(op : (B, A) => B) : B"B in this case must be a Map[String, Int], so I don't really understand the function in your example at all! It should return a Map for a start, shouldn't it?
oxbow_lakes
OK - so I've got this! "m(s) = s.length" (where m is a map) returns a new map with the mapping "s -> s.length". How was I supposed to know this? I can't find it anywhere in the programming in scala sections on maps!
oxbow_lakes
That is scala's syntactic sugar for update: An assignment f(args) = e with a function application to the left of the '=' operator is interpreted as f.update(args, e), i.e. the invocation of an update function defined by f. [The Scala Language Specification Version 2.7, 6.15 Assignments]
Palimondo
A: 

In 2.8, you can do

c map { t => (t.getP, t) } toMap
Ben Lings
I still prefer my suggestions in trac of a `Traversable[K].mapTo( K => V)` and `Traversable[V].mapBy( V => K)` were better!
oxbow_lakes