18

I tried to use Map.map to convert a map into a List of Tuples. However this fails. I did the following experiments:

val m = Map(("a" -> 1), ("b" -> 2))
         //> m  :     scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2)
val r1 = m.map{ case (k,v) => v}                //> r1  : scala.collection.immutable.Iterable[Int] = List(1, 2)
def toTuple[A,B](a:A,b:B) = (a,b)               //> toTuple: [A, B](a: A, b: B)(A, B)
//val r2: List[Tuple2[_,_]] = m.map(e => (e._1,e._2))
val r3 = m.map(e => toTuple(e._1,e._2))         //> r3  : scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2)
val r4 = m.toSeq                                //> r4  : Seq[(String, Int)] = ArrayBuffer((a,1), (b,2))

Notice how a List is generated for single elements (r1) but a Map is produced for tuples (r3). Not even forcing the type worked (r2). Only an explicit call to Seq did it (r4) So my question is, why/how does Map.map "automagically" create a new Map and not a list for example? In fact how is the return type determined (Seq, List, etc.)

cmbaxter
  • 35,283
  • 4
  • 86
  • 95
user2051561
  • 838
  • 1
  • 7
  • 21
  • 6
    What is wrong with `m.toList`? Also note that mapping on a collection returns another collection of the same type, so you can't really return a list mapping on a `Map`, unless you call `.toList` on it. – Ende Neu Feb 27 '15 at 16:08
  • @Ende - Nothing is wrong. See comment below. – user2051561 Mar 02 '15 at 09:45

2 Answers2

29

A Map is a collection of tuples already.

scala> "b" -> 2
res0: (String, Int) = (b,2) // Implicitly converted to a Tuple

When you're mapping a Map, you're mapping the (key, value) pairs that it contains. This can't work, because you're stripping away the keys, and retaining only the values. So what you have is no longer a Map, but a step or two up the collection hierarchy, an Iterable:

val r1 = m.map{ case (k,v) => v} 

Forcing the type cannot work, because a Map[A, B] is not a List[(A, B)]. This is the equivalent of m.map(identity). Notice how you're even accessing e with tuple accessors:

val r2: List[Tuple2[_,_]] = m.map(e => (e._1,e._2))

val r3 = m.map(e => toTuple(e._1,e._2))

Here, Seq is more generalized than List:

val r4 = m.toSeq

The simple solution as stated by @EndeNeu is to just use toList. When you map a collection, it should return the original collection type if it can. So mapping a Map should return another Map, unless the underlying structure has made it no longer a Map (like removing keys entirely) in r1.

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
  • Thanks m-z. My question is really: in the case of `r1`, how does `Map` know it should convert to a `List`? It could have been something else? Why not an array? I am assuming their is some implicit conversion going on here. Hence my testing by forcing types. In regards to the Tuples, makes sense. That's the Map convention. – user2051561 Mar 02 '15 at 09:42
  • @user2051561 It doesn't convert it to a `List` in `r1`--it returns `Iterable` because that's the next closest trait up the hierarchy that matches the type of collection you have. `Map` extends `Iterable`. – Michael Zajac Mar 02 '15 at 14:04
0

Why even bother with a map? Wouldn't it be more appropriate for your use case to just have your data structure be a List[(String,Int)]? It's basically the same.. you can even write it in map notation:

val myMapAsList: List[(String, Int)] = List[(String,Int)](
    "a" -> 1,
    "b" -> 2
) // yields val myMapAsList: List[(String, Int)] = List((a,1), (b,2))

Cheruvim
  • 127
  • 1
  • 5