11

我是 Scala 的新手。我在我的代码中使用了SortedMap,我想使用mapValues来创建一个对值进行一些转换的新地图。

mapValues函数不是返回一个新的SortedMap,而是返回一个新的Map,然后我必须将其转换为一个SortedMap

例如

val my_map = SortedMap(1 -> "one", 0 -> "zero", 2 -> "two")
val new_map = my_map.mapValues(name => name.toUpperCase)
// returns scala.collection.immutable.Map[Int,java.lang.String] = Map(0 -> ZERO, 1 -> ONE, 2 -> TWO)
val sorted_new_map = SortedMap(new_map.toArray:_ *)

这看起来效率低下 - 最后一次转换可能会再次对键进行排序,或者至少验证它们是否已排序。

我可以使用对键和值都进行操作的法线贴图函数,并且故意不更改转换函数中的键。这看起来也效率低下,因为Map的实现可能假设转换可能会改变键的顺序(就像在这种情况下:)my_map.map(tup => (-tup._1, tup._2)- 所以它也可能“重新排序”它们。

有谁熟悉MapSortedMap的内部实现,并且可以告诉我我的假设是否正确?编译器可以自动识别键没有被重新排序吗?为什么mapValues不应该返回SortedMap有内部原因吗?有没有更好的方法来转换地图的值而不丢失键的顺序?

谢谢

4

1 回答 1

16

您偶然发现了 ScalaMap实现的一个棘手功能。您缺少的问题是它mapValues实际上并没有返回 a new Map:它返回 aview的 a Map。换句话说,它以这样一种方式包装您的原始地图,即每当您访问一个值时,它都会.toUpperCase在将值返回给您之前进行计算。

这种行为的好处是 Scala 不会为未访问的值计算函数,也不会花时间将所有数据复制到新的Map. 缺点是每次访问该值时都会重新计算该函数。因此,如果您多次访问相同的值,您最终可能会进行额外的计算。

那么为什么不SortedMap返回 aSortedMap呢?因为它实际上返回了一个Map-wrapper。底层的Map,然后是被包装的,仍然是 a SortedMap,所以如果你要遍历它,它仍然是排序的。你我都知道,但类型检查器不知道。看起来他们当然可以以仍然保持SortedMap特征的方式编写它,但他们没有。

您可以在代码中看到它没有返回 a SortedMap,但迭代行为仍将被排序:

// from MapLike
override def mapValues[C](f: B => C): Map[A, C] = new DefaultMap[A, C] {
  def iterator = for ((k, v) <- self.iterator) yield (k, f(v))
  ...

您的问题的解决方案与解决视图问题的解决方案相同:使用.map{ case (k,v) => (k,f(v)) },正如您在问题中提到的那样。


如果你真的想要那种方便的方法,你可以做我所做的,写你自己的,更好的,版本mapValues

class EnrichedWithMapVals[T, U, Repr <: GenTraversable[(T, U)]](self: GenTraversableLike[(T, U), Repr]) {
  /**
   * In a collection of pairs, map a function over the second item of each
   * pair.  Ensures that the map is computed at call-time, and not returned
   * as a view as 'Map.mapValues' would do.
   *
   * @param f   function to map over the second item of each pair
   * @return a collection of pairs
   */
  def mapVals[R, That](f: U => R)(implicit bf: CanBuildFrom[Repr, (T, R), That]) = {
    val b = bf(self.asInstanceOf[Repr])
    b.sizeHint(self.size)
    for ((k, v) <- self) b += k -> f(v)
    b.result
  }
}
implicit def enrichWithMapVals[T, U, Repr <: GenTraversable[(T, U)]](self: GenTraversableLike[(T, U), Repr]): EnrichedWithMapVals[T, U, Repr] =
  new EnrichedWithMapVals(self)

现在,当您调用mapValsa时,SortedMap您会得到一个 non-view SortedMap

scala> val m3 = m1.mapVals(_ + 1)
m3: SortedMap[String,Int] = Map(aardvark -> 2, cow -> 6, dog -> 10)

它实际上适用于任何对集合,而不仅仅是Map实现:

scala> List(('a,1),('b,2),('c,3)).mapVals(_+1)
res8: List[(Symbol, Int)] = List(('a,2), ('b,3), ('c,4))
于 2012-09-26T21:08:36.703 回答