0

I've encountered a strange behavior regarding conversion of mutable collections to immutable ones, which might significantly affect performance.

Let's take a look at the following code:

val map: Map[String, Set[Int]] = createMap()
while (true) {
    map.get("existing-key")
}

It simply creates a map once, and then repeatedly accesses one of its enries, which contains a set as a value. It may create the map in several ways:

With immutable collections:

def createMap() = keys.map(key => key -> (1 to amount).toSet).toMap

Or with mutable collections (note the two conversion options at the end):

def createMap() = {
    val map = mutable.Map[String, mutable.Set[Int]]()
    for (key <- keys) {
        val set = map.getOrElseUpdate(key, mutable.Set())
        for (i <- 1 to amount) {
            set.add(i)
        }
    }
    map.toMap.mapValues(_.toSet) // option #1
    map.mapValues(_.toSet).toMap // option #2
}

Curiously enough, mutable #1 code creates a map which invokes toSet on its values whenever get is invoked (if the entry exists), which may introduce a significant performance hit (depending on the use-case).

Why is this happening? How can this be avoided?

Eyal Roth
  • 3,895
  • 6
  • 34
  • 45

1 Answers1

5

mapValues simply returns a map view which maps every key of this map to f(this(key)). The resulting map wraps the original map without copying any elements.

Looking at the implementation, mapValues returns an instance of MappedValues which override the get function:

def get(key: K) = self.get(key).map(f)

If you want to force the materialization of the map, call toMap after the mapValues call. Just like you did in #2!

Jean Logeart
  • 52,687
  • 11
  • 83
  • 118
  • 1
    I guess one would wonder [why mapValues produces a view](http://stackoverflow.com/questions/14882642). – Eyal Roth Dec 05 '16 at 16:36