使用可变地图或foldLeft来构建地图?

时间:2018-01-24 21:54:41

标签: scala functional-programming

我有一个案例,我会浏览大量数据并构建多个地图。我的函数的结果将是Maps

case class Maps(map1: Map[String, String], map2: Map[String, String])

我试图决定是否使用功能风格或者老式的"建立可变地图的方法。后者看起来大致像

type MutableMap = scala.collection.mutable.Map[String, String]
val MutableMap = scala.collection.mutable.Map

def buildMaps(input: Something): Maps = {
    var map1: MutableMap = MutableMap()
    var map2: MutableMap = MutableMap()
    input.getAnIterator.foreach(x => {
        map1 += (key1(x) -> val1(x))
        map2 += (key2(x) -> val2(x))
    }
    Maps(map1.toMap, map2.toMap)
}

我能看到的功能替代方案就像

def addToMaps(maps: Maps, x: SomeElement): Maps =
    Maps(maps.map1 + (key1(x) -> val1(x)), maps.map2 + (key2(x) -> val2(x)))

def buildMaps(input: Something): Maps = 
    input.getAnIterator.foldLeft(Maps(Map(), Map()))(addToMaps)

[我的语法可能不完全正确,但希望这给出了我尝试做的事情的要点。]

第二种方式似乎更多"优雅&#34 ;;但如果通过制作不可变地图的重复副本来实现它,它就不可行(我希望input非常大)。

Scala能否优化第二种解决方案,使其性能与第一种解决方案相当?我还缺少另一种方法吗?或者我应该坚持使用非功能性方法?

5 个答案:

答案 0 :(得分:1)

您还可以对2元素元组的集合执行.toMap。类似的东西:

def buildMaps(input: Something): Maps = {
  val m1 = input.getAnIterator.map(x => key1(x) -> val1(x)).toMap
  val m2 = input.getAnIterator.map(x => key2(x) -> val2(x)).toMap
  Maps(m1, m2)
}

(假设getAnIterator返回一个scala迭代器或一些scala集合)

答案 1 :(得分:1)

我想说你方法的第一次实现完全没问题。它还有一个优点,它只使用迭代器一次,并且不会遍历整个集合两次。毕竟,这就是"默认环境机器状态monad"在Scala中最适合:修改可变数据结构。如果不是这种情况,你还会在哪里使用可变变量?函数集合操作的默认实现(如mapfilter等)无论如何都使用了可变Builder

我想引用奥德斯基本人的话: https://www.youtube.com/watch?v=iPitDNUNyR0&t=34m1s。 这是ScalaDays2013的重点说明。大约30分钟左右,Odersky提出了他在小局部范围内使用可变变量的意见。我认为他的观点是:如果可变版本更快,更清晰,并且没有可变状态可以从该方法中逃脱,那么使用可变局部变量就可以了。

我怀疑Scala会自动优化第二个解决方案到第一个,我实际上怀疑第一个解决方案可能会更快一点。但是,您应该对其进行分析,然后才决定这段代码是否值得进行优化。

答案 2 :(得分:1)

正如您所看到的here,Scala的不可变HashMap实现在有效的恒定时间内进行插入:

enter image description here

所以 - 虽然您对性能的关注并非不切实际,但鉴于此信息我们可以得出结论两个版本的性能可能相当。如果是这样的话 - 我肯定会选择更安全,更简洁的功能风格。

如果您不确定在使用Map()时实际使用哪种实现,您可以专门实例化new HashMap[String, String]()以确保这是使用的实现。

答案 3 :(得分:0)

"惯用"答案可能是第三个,除非你有任何一种并发问题,但听起来我们可以把它们排除在外。 Scala的内部集合将集合构建为名为scala.collection.CanBuildFrom的东西,它为您提供了一个带有不可变result()方法的可变API。

def buildMaps(input: Something): Maps = {
  val builder = Map.newBuilder[A, B]
  val m1 = input.getAnIterator.foreach(i => builder += i -> i)
  Maps(builder.result())
}

基本上这是引擎盖下使用的模式,所以它使用最低级别的API。

答案 4 :(得分:0)

我倾向于采用函数式方法,因为不可变Map(特别是HashMap)在查找/添加/删除方面具有设计效率,正如其他人所指出的那样。

至于关注re:复制不可变地图的成本,我的理解是内部这些地图不是字面上复制的。如果您查看HashMap的源代码,您会注意到它是使用HashTrieMap实现的。 hash trie数据结构的一个重要特征是,在更新时,只会重写从根到存储密钥的叶子的路径。 trie的其余部分保持不变。这是关于hash trie的文档。