1

I have the following Array[Int]: val array = Array(1, 2, 3), for which I have the following mapping relation between an Int and a String:

val a1 = array.map{
  case 1 => "A"
  case 2 => "B"
  case 3 => "C"
}

To create a Map to contain the above mapping relation, I am aware that I can use a foldLeft method:

val a2 = array.foldLeft(Map[String, Int]()) { (m, e) =>
  m + (e match {
    case 1 => ("A", 1)
    case 2 => "B" -> 2
    case 3 => "C" -> 3
  })
}

which outputs:

a2: scala.collection.immutable.Map[String,Int] = Map(A -> 1, B -> 2, C -> 3)

This is the result I want. But can I achieve the same result via the map method?

The following codes do not work:

val a3 = array.map[(String, Int), Map[String, Int]] {
  case 1 => ("A", 1)
  case 2 => ("B", 2)
  case 3 => ("C", 3)
}

The signature of map is

def map[B, That](f: A => B)
(implicit bf: CanBuildFrom[Repr, B, That]): That

What is this CanBuildFrom[Repr, B, That]? I tried to read Tribulations of CanBuildFrom but don't really understand it. That article mentioned Scala 2.12+ has provided two implementations for map. But how come I didn't find it when I use Scala 2.12.4?

I mostly use Scala 2.11.12.

3 Answers 3

3

Call toMap in the end of your expression:

val a3 = array.map {
  case 1 => ("A", 1)
  case 2 => ("B", 2)
  case 3 => ("C", 3)
}.toMap
Sign up to request clarification or add additional context in comments.

2 Comments

This does not work. I still got the same errors: Error:(36, 126) Cannot construct a collection of type Map[String,Int] with elements of type (String, Int) based on a collection of type Array[Int]. def get$$instance$$a3 = a3;/* ###worksheet### generated $$end$$ */ lazy val a3 = array.map[(String, Int), Map[String, Int]] { ^
And Error:(36, 126) not enough arguments for method map: (implicit bf: scala.collection.generic.CanBuildFrom[Array[Int],(String, Int),Map[String,Int]])Map[String,Int]. Unspecified value parameter bf. def get$$instance$$a3 = a3;/* ###worksheet### generated $$end$$ */ lazy val a3 = array.map[(String, Int), Map[String, Int]] { ^
2

I'll first define your function here for the sake of brevity in later explanation:

// worth noting that this function is effectively partial
// i.e. will throw a `MatchError` if n is not in (1, 2, 3)
def toPairs(n: Int): (String, Int) =
  n match {
    case 1 => "a" -> 1
    case 2 => "b" -> 2
    case 3 => "c" -> 3
  }

One possible way to go (as already highlighted in another answer) is to use toMap, which only works on collection of pairs:

val ns = Array(1, 2, 3)

ns.toMap // doesn't compile

ns.map(toPairs).toMap // does what you want

It is worth noting however that unless you are working with a lazy representation (like an Iterator or a Stream) this will result in two passes over the collection and the creation of unnecessary intermediate collections: the first time by mapping toPairs over the collection and then by turning the whole collection from a collection of pairs to a Map (with toMap).

You can see it clearly in the implementation of toMap.

As suggested in the read you already linked in the answer (and in particular here) You can avoid this double pass in two ways:

  • you can leverage scala.collection.breakOut, an implementation of CanBuildFrom that you can give map (among others) to change the target collection, provided that you explicitly provide a type hint for the compiler:

val resultMap: Map[String, Int] = ns.map(toPairs)(collection.breakOut)
val resultSet: Set[(String, Int)] = ns.map(toPairs)(collection.breakOut)

  • otherwise, you can create a view over your collection, which puts it in the lazy wrapper that you need for the operation to not result in a double pass

ns.view.map(toPairs).toMap

You can read more about implicit builder providers and views in this Q&A.

Comments

2

Basically toMap (credits to Sergey Lagutin) is the right answer.

You could actually make the code a bit more compact though:

val a1 = array.map { i => ((i + 64).toChar, i) }.toMap

If you run this code:

val array = Array(1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 0)
val a1 = array.map { i => ((i + 64).toChar, i) }.toMap
println(a1)

You will see this on the console:

Map(E -> 5, J -> 10, F -> 6, A -> 1, @ -> 0, G -> 7, L -> 12, B -> 2, C -> 3, H -> 8, K -> 11, D -> 4)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.