我是Scala的新手。 我在我的代码中使用 SortedMap ,我想使用 mapValues 创建一个新的地图,并对值进行一些转换。
mapValues 函数返回一个新的 Map ,而不是返回一个新的 SortedMap ,然后我必须将其转换为a 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 函数,该函数同时对键和值进行操作,故意不改变转换函数中的键。这看起来效率也很低,因为 Map 的实现可能假设转换可能会改变键的顺序(例如:my_map.map(tup => (-tup._1, tup._2)
) - 所以它可能会“重新排序”他们也是。
是否有人熟悉 Map 和 SortedMap 的内部实现,并且可以告诉我我的假设是否正确?编译器是否可以自动识别密钥尚未重新排序?是否有一个内部原因导致 mapValues 不应该返回 SortedMap ?有没有更好的方法来转换地图的值而不会丢失键的顺序?
由于
答案 0 :(得分:16)
你偶然发现了Scala的Map
实现的棘手功能。您遗失的问题是mapValues
实际上并未返回新的Map
:它会返回view
的{{1}}。换句话说,它会以这样的方式包装原始地图:无论何时访问某个值,它都会在将值返回给您之前计算Map
。
此行为的好处是Scala不会为未访问的值计算函数,也不会花时间将所有数据复制到新的.toUpperCase
中。缺点是每次访问该值时都会重新计算函数。因此,如果多次访问相同的值,最终可能会进行额外的计算。
那么Map
为什么不返回SortedMap
?因为它实际上返回了SortedMap
- 包装器。底层的Map
,然后是一个被包装的Map
仍然是SortedMap
,所以如果你要迭代,它仍然是按排序顺序。你和我都知道,但是类型检查器没有。看起来他们似乎已经以这样的方式编写它仍然保持SortedMap
特征,但他们没有。
您可以在代码中看到它没有返回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)
现在当您在mapVals
上致电SortedMap
时,您会收到一个非查看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))