Scala best way of turning a Collection into a Map-by-key?

ScalaMapScala Collections

Scala Problem Overview


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)

Scala Solutions


Solution 1 - Scala

You can use

c map (t => t.getP -> t) toMap

but be aware that this needs 2 traversals.

Solution 2 - Scala

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)

Solution 3 - Scala

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 }

Solution 4 - Scala

This can be implemented immutably and with a single traversal by folding through the collection as follows.

val map = c.foldLeft(Map[P, T]()) { (m, t) => m + (t.getP -> t) }

The solution works because adding to an immutable Map returns a new immutable Map with the additional entry and this value serves as the accumulator through the fold operation.

The tradeoff here is the simplicity of the code versus its efficiency. So, for large collections, this approach may be more suitable than using 2 traversal implementations such as applying map and toMap.

Solution 5 - Scala

Another solution (might not work for all types)

import scala.collection.breakOut
val m:Map[P, T] = c.map(t => (t.getP, t))(breakOut)

this avoids the creation of the intermediary list, more info here: https://stackoverflow.com/questions/1715681/scala-2-8-breakout

Solution 6 - Scala

What you're trying to achieve is a bit undefined.
What if two or more items in c share the same p? Which item will be mapped to that p in the map?

The more accurate way of looking at this is yielding a map between p and all c items that have it:

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

This could be easily achieved with groupBy:

val m: Map[P, Collection[T]] = c.groupBy(t => t.p)

If you still want the original map, you can, for instance, map p to the first t that has it:

val m: Map[P, T] = c.groupBy(t => t.p) map { case (p, ts) =>  p -> ts.head }

Solution 7 - Scala

Scala 2.13+

instead of "breakOut" you could use

c.map(t => (t.getP, t)).to(Map)

Scroll to "View": https://www.scala-lang.org/blog/2017/02/28/collections-rework.html

Solution 8 - Scala

c map (_.getP) zip c

Works well and is very intuitiv

Solution 9 - Scala

This is probably not the most efficient way to turn a list to map, but it makes the calling code more readable. I used implicit conversions to add a mapBy method to List:

implicit def list2ListWithMapBy[T](list: List[T]): ListWithMapBy[T] = {
  new ListWithMapBy(list)
}

class ListWithMapBy[V](list: List[V]){
  def mapBy[K](keyFunc: V => K) = {
    list.map(a => keyFunc(a) -> a).toMap
  }
}

Calling code example:

val list = List("A", "AA", "AAA")
list.mapBy(_.length)                  //Map(1 -> A, 2 -> AA, 3 -> AAA)

Note that because of the implicit conversion, the caller code needs to import scala's implicitConversions.

Solution 10 - Scala

How about using zip and toMap?

myList.zip(myList.map(_.length)).toMap

Solution 11 - Scala

For what it's worth, here are two pointless ways of doing it:

scala> case class Foo(bar: Int)
defined class Foo

scala> import scalaz._, Scalaz._
import scalaz._
import Scalaz._

scala> val c = Vector(Foo(9), Foo(11))
c: scala.collection.immutable.Vector[Foo] = Vector(Foo(9), Foo(11))

scala> c.map(((_: Foo).bar) &&& identity).toMap
res30: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11))

scala> c.map(((_: Foo).bar) >>= (Pair.apply[Int, Foo] _).curried).toMap
res31: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11))

Solution 12 - Scala

This works for me:

val personsMap = persons.foldLeft(scala.collection.mutable.Map[Int, PersonDTO]()) {
    (m, p) => m(p.id) = p; m
}

The Map has to be mutable and the Map has to be return since adding to a mutable Map does not return a map.

Solution 13 - Scala

use map() on collection followed with toMap

val map = list.map(e => (e, e.length)).toMap

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionoxbow_lakesView Question on Stackoverflow
Solution 1 - ScalaBen LingsView Answer on Stackoverflow
Solution 2 - ScalaJames IryView Answer on Stackoverflow
Solution 3 - ScalaDaniel SpiewakView Answer on Stackoverflow
Solution 4 - ScalaRamV13View Answer on Stackoverflow
Solution 5 - ScalaSomatikView Answer on Stackoverflow
Solution 6 - ScalaEyal RothView Answer on Stackoverflow
Solution 7 - ScalaRonRubyView Answer on Stackoverflow
Solution 8 - ScalaJörg BächtigerView Answer on Stackoverflow
Solution 9 - ScalaErezView Answer on Stackoverflow
Solution 10 - ScalaAwesomeBobX64View Answer on Stackoverflow
Solution 11 - ScalamissingfaktorView Answer on Stackoverflow
Solution 12 - ScalarustyfingerView Answer on Stackoverflow
Solution 13 - ScalaKrishna Kumar ChourasiyaView Answer on Stackoverflow