在Map中,mapValues和transform有什么区别?

37
在Scala的Map中(参见API),mapValuestransform在语义和性能方面有什么区别?对于任何给定的地图,例如:
val m = Map( "a" -> 2, "b" -> 3 )

两者都

m.mapValues(_ * 5)
m.transform( (k,v) => v * 5 )

达到相同的结果。

3个回答

59

假设我们有一个 Map[A,B]。为了澄清:我总是指的是不可变的 Map

mapValues 接受一个函数 B => C,其中 C 是新值的类型。

transform 接受一个函数 (A, B) => C,其中这个 C 也是值的类型。

所以两者都会产生一个 Map[A,C]

然而,使用 transform 函数,您可以通过它们的键的值来影响新值的结果。

例如:

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

使用mapValues来完成这个操作会非常困难。

下一个区别在于,transform是严格的,而mapValues只会给你一个视图,不会存储更新后的元素。它看起来像这样:

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)
}

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

所以从性能上来说,这要取决于哪种方式更有效。如果f很昂贵,而且您只访问结果映射的一些元素,则mapValues可能更好,因为f只在需要时应用。否则,我会坚持使用maptransform

transform也可以用map表示。 假设m:Map [A,B]f:(A,B)=> C,那么

m.transform(f)等同于m.map {case (a,b) =>(a,f(a,b))}


非常棒的回答!谢谢! - SkyWalker
请注意,如果 f 很耗费资源,并且访问结果映射的元素很少,那么使用 mapValues 可能更好,因为 f 仅在需要时应用。但是要小心,如果 f 很耗费资源并且元素被频繁访问,则在性能方面使用 mapValues 很差。 - Mahdi

11

collection.Map没有提供transform函数:对于可变和不可变映射,它们的签名是不同的。

$ 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

可变变换会就地进行改变:

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)

因此,可变转换不会更改地图的类型:

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)

...在构建新地图时可能会发生。


7
这里有一些未提及的区别:
  • mapValues创建的Map是不可序列化的,没有任何迹象表明它只是一个视图(类型是Map[_, _],但试着将其发送到另外一台机器上看看)。

  • 由于mapValues只是一个视图,因此每个实例都包含了真正的Map - 这可能是mapValues的另一个结果。想象一下,你有一个带有某些状态的actor,每次状态变化后,新的状态都被设置为前一个状态上的一个mapValues......最终你会得到深度嵌套的地图,并复制每个先前状态的actor(是的,这两个都是从经验中得来的)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接