Difference between mapValues and transform in Map

前端 未结 3 484
甜味超标
甜味超标 2020-12-24 02:23

In Scala Map (see API) what is the difference in semantics and performance between mapValues and transform ?

For any given map

相关标签:
3条回答
  • 2020-12-24 02:35

    collection.Map doesn't provide transform: it has a different signature for mutable and immutable Maps.

    $ scala
    Welcome to Scala version 2.11.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_11).
    Type in expressions to have them evaluated.
    Type :help for more information.
    
    scala> val im = Map('a -> 1, 'b -> 2, 'c -> 3)
    im: scala.collection.immutable.Map[Symbol,Int] = Map('a -> 1, 'b -> 2, 'c -> 3)
    
    scala> im.mapValues(_ * 7) eq im
    res0: Boolean = false
    
    scala> im.transform { case (k,v) => v*7 } eq im
    res2: Boolean = false
    
    scala> val mm = collection.mutable.Map('a -> 1, 'b -> 2, 'c -> 3)
    mm: scala.collection.mutable.Map[Symbol,Int] = Map('b -> 2, 'a -> 1, 'c -> 3)
    
    scala> mm.mapValues(_ * 7) eq mm
    res3: Boolean = false
    
    scala> mm.transform { case (k,v) => v*7 } eq mm
    res5: Boolean = true
    

    Mutable transform mutates in place:

    scala> mm.transform { case (k,v) => v*7 }
    res6: mm.type = Map('b -> 98, 'a -> 49, 'c -> 147)
    
    scala> mm.transform { case (k,v) => v*7 }
    res7: mm.type = Map('b -> 686, 'a -> 343, 'c -> 1029)
    

    So mutable transform doesn't change the type of the map:

    scala> im mapValues (_ => "hi")
    res12: scala.collection.immutable.Map[Symbol,String] = Map('a -> hi, 'b -> hi, 'c -> hi)
    
    scala> mm mapValues (_ => "hi")
    res13: scala.collection.Map[Symbol,String] = Map('b -> hi, 'a -> hi, 'c -> hi)
    
    scala> mm.transform { case (k,v) => "hi" }
    <console>:9: error: type mismatch;
     found   : String("hi")
     required: Int
                  mm.transform { case (k,v) => "hi" }
                                               ^
    
    scala> im.transform { case (k,v) => "hi" }
    res15: scala.collection.immutable.Map[Symbol,String] = Map('a -> hi, 'b -> hi, 'c -> hi)
    

    ...as can happen when constructing a new map.

    0 讨论(0)
  • 2020-12-24 02:35

    Here's a couple of unmentioned differences:

    • mapValues creates a Map that is NOT serializable, without any indication that it's just a view (the type is Map[_, _], but just try to send one across the wire).

    • Since mapValues is just a view, every instance contains the real Map - which could be another result of mapValues. Imagine you have an actor with some state, and every mutation of the state sets the new state to be a mapValues on the previous state...in the end you have deeply nested maps with a copy of each previous state of the actor (and, yes, both of these are from experience).

    0 讨论(0)
  • 2020-12-24 02:41

    Let's say we have a Map[A,B]. For clarification: I'm always referring to an immutable Map.

    mapValues takes a function B => C, where C is the new type for the values.

    transform takes a function (A, B) => C, where this C is also the type for the values.

    So both will result in a Map[A,C].

    However with the transform function you can influence the result of the new values by the value of their keys.

    For example:

    val m = Map( "a" -> 2, "b" -> 3 )
    m.transform((key, value) => key + value) //Map[String, String](a -> a2, b -> b3)
    

    Doing this with mapValues will be quite hard.

    The next difference is that transform is strict, whereas mapValues will give you only a view, which will not store the updated elements. It looks like this:

    protected class MappedValues[C](f: B => C) extends AbstractMap[A, C] with DefaultMap[A, C] {
      override def foreach[D](g: ((A, C)) => D): Unit = for ((k, v) <- self) g((k, f(v)))
      def iterator = for ((k, v) <- self.iterator) yield (k, f(v))
      override def size = self.size
      override def contains(key: A) = self.contains(key)
      def get(key: A) = self.get(key).map(f)
    }
    

    (taken from https://github.com/scala/scala/blob/v2.11.2/src/library/scala/collection/MapLike.scala#L244)

    So performance-wise it depends what is more effective. If f is expensive and you only access a few elements of the resulting map, mapValues might be better, since f is only applied on demand. Otherwise I would stick to map or transform.

    transform can also be expressed with map. Assume m: Map[A,B] and f: (A,B) => C, then

    m.transform(f) is equivalent to m.map{case (a, b) => (a, f(a, b))}

    0 讨论(0)
提交回复
热议问题