【问题标题】:Getting rid of for loops in Scala摆脱 Scala 中的 for 循环
【发布时间】:2018-10-12 04:30:49
【问题描述】:

这是一个涉及factorial 的问题。对于给定的号码n,请找到以下问题的答案:

(1 / n!) * (1! + 2! + 3! + ... + n!)

Scala 中的迭代解决方案非常简单——一个简单的 for 循环就足够了。

object MyClass {

def fsolve(n: Int): Double = {
    var a: Double = 1
    var cum: Double = 1
    for (i <- n to 2 by -1) { 
        a = a * (1.0/i.toDouble)
        cum += a
    }
    scala.math.floor(cum*1000000) / 1000000
}

def main(args: Array[String]) {
    println(fsolve(7))     // answer 1.173214
}

}

我想摆脱 for 循环并使用 foldLeft 操作。由于这个想法是将数字列表减少为单个结果,foldLeft 或类似的指令应该可以完成这项工作。如何?我正在努力寻找一个可以效仿的好的 Scala 示例。下面的代码说明了我正在努力实现向更惯用的 Scala 的飞跃。

object MyClass {

    def fsolve(n: Int) = {
        (n to 2 by -1).foldLeft(1.toDouble) (_*_)
        // what comes next????
    }

    def main(args: Array[String]) {
        println(fsolve(7))
    }
}

对解决方案有任何建议或指示吗?

【问题讨论】:

    标签: scala functional-programming


    【解决方案1】:

    结果从foldLeft返回,像这样:

    val cum = (n to 2 by -1).foldLeft(1.toDouble) (_*_)
    

    仅在您的情况下,函数需要有所不同,因为上面的折叠会将所有 i 值相乘。您将为折叠传递 cuma 值:

    def fsolve(n: Int): Double = {
      val (cum, _) = (n to 2 by -1).foldLeft(1.0, 1.0) { case ((sum, a),i) =>
        val newA = a * (1.0/i.toDouble)
        (sum + newA, newA)
      }
      scala.math.floor(cum*1000000) / 1000000
    }
    

    【讨论】:

    • 此解决方案适用于大小数字,例如200. 谢谢。
    【解决方案2】:

    您提供的公式很好地映射到scanLeft 函数。它的工作方式有点像foldLeftmap 的组合,运行折叠操作,但将每个生成的值存储在输出列表中。以下代码生成从1n 的所有阶乘,将它们相加,然后除以n!。请注意,通过在最后而不是在每个中间步骤执行单个浮点除法,可以降低浮点错误的几率。

    def fsolve(n: Int): Double =
    {
      val factorials = (2 to n).scanLeft(1)((cum: Int, value: Int) => value*cum)
      scala.math.floor(factorials.reduce(_+_)/factorials.last.toDouble*1000000)/1000000
    }
    

    【讨论】:

      【解决方案3】:

      我将尝试实施一个解决方案,而不是填补空白,而是提出一种不同的方法。

      def fsolve(n: Int): Double = {
        require(n > 0, "n must be positive")
        def f(n: Int): Long = (1 to n).fold(1)(_ * _)
        (1.0 / f(n)) * ((1 to n).map(f).sum)
      }
      

      在函数中,我确保使用require 的无效输入会失败,我定义阶乘(如f),然后通过简单地以最接近我们想要的原始表达式的方式写下函数来使用它实施:

      (1.0 / f(n)) * ((1 to n).map(f).sum)
      

      如果你真的想显式折叠你可以重写这个表达式如下:

      (1.0 / f(n)) * ((1 to n).map(f).fold(0L)(_ + _))
      

      另外,请注意,由于您正在执行的所有操作(加法和乘法)都是可交换的,因此您可以使用fold 而不是foldLeft:使用前者并没有规定操作应该运行的顺序,允许集合的特定实现以并行运行计算。

      您可以使用此代码here on Scastie

      【讨论】:

      • 请注意:此解决方案的复杂度为 O(N^2),因为阶乘的计算方式。原始代码为 O(N)。这可能是也可能不是问题,具体取决于使用情况。
      猜你喜欢
      • 1970-01-01
      • 2020-08-04
      • 2014-02-12
      • 1970-01-01
      • 1970-01-01
      • 2017-06-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多