【问题标题】:Scala: Why mapValues produces a view and is there any stable alternatives?Scala:为什么 mapValues 会产生一个视图并且有任何稳定的替代方案吗?
【发布时间】:2019-11-11 23:45:25
【问题描述】:

刚刚得知mapValues 产生了一个视图,我感到很惊讶。结果如下例所示:

case class thing(id: Int)
val rand = new java.util.Random
val distribution = Map(thing(0) -> 0.5, thing(1) -> 0.5)
val perturbed = distribution mapValues { _ + 0.1 * rand.nextGaussian }
val sumProbs = perturbed.map{_._2}.sum
val newDistribution = perturbed mapValues { _ / sumProbs }

我的想法是我有一个分布,它受到一些随机性的干扰,然后我重新规范化它。该代码实际上在其初衷中失败了:由于mapValues 产生了view,所以每当使用perturbed 时,总是会重新评估_ + 0.1 * rand.nextGaussian

我现在正在做类似distribution map { case (s, p) => (s, p + 0.1 * rand.nextGaussian) } 的事情,但这有点冗长。所以这个问题的目的是:

  1. 提醒不知道这一事实的人。
  2. 找出他们让mapValues 输出views 的原因。
  3. 是否有替代方法可以产生混凝土Map
  4. 有没有其他常用的收集方法有这个陷阱。

谢谢。

【问题讨论】:

  • 天哪,我在另一个地方使用mapValues,这就是它有时运行如此缓慢的原因......
  • 我也被这个咬过。
  • 今天我也中了这个陷阱。
  • 今天刚被这个咬了!
  • 我知道这种行为,但我仍然陷入了这个陷阱,更糟糕的是,调试起来非常痛苦。

标签: scala map


【解决方案1】:

有一张关于这个的票,SI-4776(YT)。

介绍它的提交有这样的说法:

根据 jrudolph 的建议,制作了 filterKeysmapValues 转换抽象映射和不可变的重复功能 地图。将 transformfilterNot 从不可变映射移动到通用映射。 由 phaller 审查。

我无法找到 jrudolph 的原始建议,但我认为这样做是为了提高 mapValues 的效率。提出问题,这可能会让您感到意外,但如果您不太可能多次迭代这些值,mapValues 会更有效。

作为一种变通方法,可以使用mapValues(...).view.force 生成一个新的Map

【讨论】:

  • 很好,但我仍然想知道为什么 mapValues 不直接返回视图以使其更明确?
  • @AloisCochard 是的,这很好。如果返回类型是 view 会更好,这样我们就会知道那里发生了什么......
  • @AloisCochard 正如您在票证中看到的,这正是我所要求的。让force 方法直接可用会有更多好处。
  • 我看不出这个决定背后的原因。仅当(现有键的)调用次数少于条目数时,使用视图才能提高性能;根据我的经验,在大多数情况下几乎不是这种情况。如果底层数据是可变的(参见 SQL),视图还有其他优势,这在 Scala 中不太可取,因为它违背了函数式方法。
【解决方案2】:

scala 文档说:

一个地图视图,它将地图的每个key 映射到f(this(key))。生成的地图包装了原始地图,而不复制任何元素。

所以这应该是意料之中的,但这让我很害怕,我明天必须审查一堆代码。我没想到会有这样的行为:-(

只是另一种解决方法:

你可以调用toSeq来获取一个副本,如果你需要它来映射toMap,但是这不必要的创建对象,并且比使用map有性能影响

相对容易写,mapValues 不创建视图,我明天做,如果没有人在我之前做,我会在这里发布代码;)

编辑:

我找到了一种“强制”视图的简单方法,在 mapValues 之后使用“.map(identity)”(因此无需实现特定功能):

scala> val xs = Map("a" -> 1, "b" -> 2)
xs: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 1, b -> 2)

scala> val ys = xs.mapValues(_ + Random.nextInt).map(identity)
ys: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 1315230132, b -> 1614948101)

scala> ys
res7: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 1315230132, b -> 1614948101)

很遗憾返回的类型实际上不是视图!否则就可以称之为“力”……

【讨论】:

  • 使用 Scala 2.12.0-M3 运行,我不清楚 map(identity) 给你带来了什么:Map("a" -> 1, "b" -> 2).mapValues(_ + Random.nextInt) 返回 scala.collection.immutable.Map[String,Int] = Map(a -> 1496073565, b -> -1842623900)。你能详细说明一下吗?我认为 mapValues 的潜在问题是它懒惰地评估值,但我不确定。谢谢
  • 这真是令人惊讶。视图的地图如何返回具体地图而不是视图?这超出了不一致的范围。这完全是缺乏考虑。从源码确认这个东西在2.12还存在。
【解决方案3】:

在 scala 2.13 中更好(并且已弃用),现在返回 MapView: API Doc

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-22
    • 1970-01-01
    • 1970-01-01
    • 2019-03-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多