【问题标题】:Incrementing Variable in a foldLeftfoldLeft 中的递增变量
【发布时间】:2013-02-20 01:43:17
【问题描述】:

我有这样的 Scala 代码

var i = 1
for(e <- array) {
    acc += e * i
    i += 1
}

我需要将数组中的第一个元素乘以 1,下一个元素乘以 2,下一个元素乘以 3,依此类推,将其全部添加到累加器中。我觉得在 Scala 中有更好的方法来做到这一点,甚至可以折叠?

【问题讨论】:

    标签: arrays scala increment accumulator


    【解决方案1】:
    val x = List(1,1,1,1,1,1)
    (((0,1) /: x){case ((acc, mult), l) => (acc + (l * mult), mult + 1) })._1
    

    换句话说,从 0 的累加器和 1 的乘数开始,将列表的每个元素折叠起来,将累加器更改为 acc + (l * mult) 并将乘数加 1。我们在最后得到最终的乘数同样,所以我们调用._1 来获取累加器。

    编辑:正如@RexKerr 在下面的回答(和评论)中指出的那样,如果性能是一个主要问题,那么最好使用显式递归方法。

    【讨论】:

      【解决方案2】:

      我更喜欢阅读更简单的 zipWithIndex:

      array.zipWithIndex.map { case (e, i) => e * (i + 1) }.sum
      

      【讨论】:

      • 但是根据我对该方法的理解,您对数组进行了两次迭代,对吗?
      • 三次。 zipWithIndex 和 map 都遍历数组,sum 是通过 foldLeft 实现的。所以我的方法肯定不是最快的
      • 如果你先在数组上使用.view,它只会通过一次,但你必须处理.view的开销。选择你的毒药。
      • @nnythm 你能扩展一下这个答案吗?为什么使用 .view 可以消除编译器对 .zipWithIndex、.map 和 .sum 数组迭代 3 次的需要?
      • @chrissphinx .view 使函数被懒惰地评估,所以我不认为 zipWithIndex 和地图直到总和时间才会评估。那时,新数组中被求和的每一项都被逐一计算,因此数组只迭代一次。
      【解决方案3】:

      我不确定我建议的方法是否更好,因为它更实用(== 执行速度会更慢):

      (0 /: (array zipWithIndex)) {(a, i) => (i._1 * (i._2 + 1)) + a}

      这确实对 http://www.scala-lang.org/api/current/index.html#scala.Array 中的 zipWithIndex 方法生成的数组执行 foldLeft

      zipWithIndex 只是用它们的索引压缩集合的元素。

      【讨论】:

        【解决方案4】:

        “更好”取决于您的目标是什么。简短而清晰?可能

        { for (i <- array.indices; e = array(i)) yield (i+1)*e }.sum
        

        array.indices.map(i => (i+1)*array(i)).sum
        

        (或者稍微快一点,因为您可以随时创建中间体:

        array.indices.iterator.map(i => (i+1)*array(i)).sum
        

        )。

        你通常应该简短而清晰。

        快吗?然后你需要去老派:

        var i = 0
        var acc = 0
        while (i < array.length) {
          acc += (i+1)*array(i)
          i += 1
        }
        

        或使用递归

        def sum(a: Array[Int], i: Int = 0, acc: Int = 0): Int =
          if (i >= a.length) acc else sum(a, i+1, (i+1)*a(i) + acc)
        sum(array)
        

        【讨论】:

        • 显式递归可能比折叠更快吗?我能看到的唯一优点是不分配元组......
        • @Impredicative - 如果您正在执行轻量级操作,例如整数相加,那么不分配元组是巨大的(10 倍加速)。此外,集合并非专门用于原始类型,这意味着必须对每个整数进行装箱,而递归可以使用原始类型。也很大(两者加起来可以提供约 20 到 30 倍的加速)。
        • 嘿@Rex,您介意解释一下为什么在第三个示例中映射迭代器吗?映射索引似乎可以解决问题。
        • @DaMainBoss - 第二个示例创建了一个全新的集合(实际上是Vector)来存储中间结果。使用iterator 可以防止这种情况——它会即时进行映射——所以尽管从一个项目推进到另一个项目会产生额外的开销,但总体而言它更快。但它没有那么短和清晰,所以只有在额外收集的开销有问题时才这样做。 (不过,我认为它比折叠更清晰。)
        • 酷,我现在会开始更频繁地使用它...干杯!
        猜你喜欢
        • 2011-11-20
        • 1970-01-01
        • 1970-01-01
        • 2021-09-06
        • 1970-01-01
        • 2017-03-03
        • 2021-10-02
        • 1970-01-01
        相关资源
        最近更新 更多