`filterKeys`和`mapValues`多次执行

时间:2017-04-02 21:35:41

标签: scala scala-collections

当我在filterKeys上使用mapValuesMap来创建新的Map时,我传递给这些方法的函数似乎每次都会被执行我使用新的Map。他们不应该只执行一次来生成新的Map

在此示例中,查看println多次发生的情况。

// printlns happen when creating the map
scala> val myMap = Map(1 -> 1, 2 -> 2, 3 -> 3).filterKeys { i => 
    println(s"filterKeys$i")
    i < 5
  }.mapValues { i =>
    println(s"mapValues$i")
    i + 1
  }
filterKeys1
mapValues1
filterKeys2
mapValues2
filterKeys3
mapValues3
myMap: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 2 -> 3, 3 -> 4)

// printlns happen again!
scala> myMap.toString()
filterKeys1
mapValues1
filterKeys2
mapValues2
filterKeys3
mapValues3
res29: String = Map(1 -> 2, 2 -> 3, 3 -> 4)

// and again!
scala> myMap + (4 -> 5)
filterKeys1
mapValues1
filterKeys2
mapValues2
filterKeys3
mapValues3
res30: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 2 -> 3, 3 -> 4, 4 -> 5)

我希望行为与.map()相同。传递给map的函数只为每个项目运行一次,并且在将来使用生成的映射时不再运行:

// printlns happen when creating the map
scala> val myMap = Map(1 -> 1, 2 -> 2, 3 -> 3).map { i =>
  println(s"map$i")
  i
}
map(1,1)
map(2,2)
map(3,3)
myMap: scala.collection.immutable.Map[Int,Int] = Map(1 -> 1, 2 -> 2, 3 -> 3)

// printlns not run again :)
scala> myMap.toString
res32: String = Map(1 -> 1, 2 -> 2, 3 -> 3)

1 个答案:

答案 0 :(得分:1)

scaladocs for mapValuesfilterKeys都提到它们会返回包含原始地图而不复制任何元素的结果地图。您可以在源代码中看到mapValues返回MappedValues的实例,该实例会针对每次重复的foreachiteratorget调用重新运行您的函数。

由于此行为与.map()不一致,因此可以打开票证SI-4776重命名方法或更改其返回类型以使其更加明显,但票证已打开很长时间,所以不要指望这种行为很快就会改变。

要解决此问题,您可以在使用这些方法强制创建新.view.force之后附加Map,或者只使用.map()代替.mapValues().filter()代替.filterKeys()。以下是附加.view.force的示例:

// printlns happen when creating the map
scala> val myMap = Map(1 -> 1, 2 -> 2, 3 -> 3).filterKeys { i => 
    println(s"filterKeys$i")
    i < 5
  }.mapValues { i =>
    println(s"mapValues$i")
    i + 1
  }.view.force
filterKeys1
mapValues1
filterKeys2
mapValues2
filterKeys3
mapValues3
myMap: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 2 -> 3, 3 -> 4)

// no printlns when the Map is used again!
scala> myMap.toString
res40: String = Map(1 -> 2, 2 -> 3, 3 -> 4)