【问题标题】:Sums of different lists using recursion使用递归的不同列表的总和
【发布时间】:2019-11-30 17:11:10
【问题描述】:

我又在做 CodeWars 挑战,今天我遇到了这个问题:

让我们考虑这个例子(以一般格式编写的数组):

ls = [0, 1, 3, 6, 10]

它的以下部分:

ls = [0, 1, 3, 6, 10]
ls = [1, 3, 6, 10]
ls = [3, 6, 10]
ls = [6, 10]
ls = [10]
ls = []

相应的总和是(放在一个列表中):[20, 20, 19, 16, 10, 0]

函数parts_sums(或它在其他语言中的变体)将把一个列表ls作为参数,并返回一个如上定义的其部分总和的列表。

object SumsOfParts {

  def partsSums(l: List[Int]): List[Int] = {

    var x: List[Int] = List()
    if (l.isEmpty)
      x
    else {
      x :+ l.sum
      partsSums(l.tail)
    }
  }
}

这里是测试样本:

partsSums(List(0, 1, 3, 6, 10)) 应该返回 List(20, 20, 19, 16, 10, 0) 测试失败

空列表的尾部 堆栈跟踪 4ms完成 partsSums(List(1, 2, 3, 4, 5, 6)) 应该返回 List(21, 20, 18, 15, 11, 6, 0) 测试失败

空列表的尾部 堆栈跟踪 Partssums(列表(744125,935,407,454,430,40,144,671213,889,810,2579358)应返回列表(10037855,929330,9292795,929388,9291934,9291504,9291414,9291270,2581057,2580168 , 2579358, 0) 测试失败

空列表的尾部 堆栈跟踪 1ms内完成 Partssums(列表(30350,76431,156228,78043,98977,80169,32457,17508,57971,17508,57971,171907)应该返回列表(1145239,1114889,1038458,882230,804187,705210,625041,592584,409709 , 247386, 229878, 171907, 0) 测试失败

【问题讨论】:

  • 1) 请修复缩进:它发生了什么? 2)ls.scanRight(0)(_ + _).
  • 也许ls.tails.map(_.sum) ?不过,该解决方案不会是最高效的。
  • 抱歉缩进,但编译器在问题开始时将列表误认为是代码,因此是输出。我修复了一些位,现在应该会更好。
  • 在运行到 SO 之前你真的应该做一些简单的调试。每次迭代都会创建一个新的空x,这就是为什么它总是返回一个空List
  • 为什么要递归? CodeWars 是否关心它是否是递归的?

标签: scala list recursion functional-programming int


【解决方案1】:

正如@Andrey 评论的scanLeft 解决了这个问题,但这里有一个递归解决方案:


  def partsSums(l: List[Int]): List[Int] = {
    if (l.isEmpty) {
      Nil
    } else {

      def go(list: List[Int], acc: List[Int], currentSum: Int): List[Int] =

        list match {
          case Nil => (0 :: acc).reverse
          case x :: xs =>
            go(xs, currentSum :: acc, currentSum - x)
        }

      go(l, Nil, l.sum)
    }
  }

  def main(args: Array[String]): Unit = {
    println(partsSums(List(0, 1, 3, 6, 10)))
    // List(20, 20, 19, 16, 10, 0)
    println(partsSums(List(1, 2, 3, 4, 5, 6)))
    // List(21, 20, 18, 15, 11, 6, 0)
    println(partsSums(List(744125, 935, 407, 454, 430, 90, 144, 6710213, 889, 810, 2579358)))
    // List(10037855, 9293730, 9292795, 9292388, 9291934, 9291504, 9291414, 9291270, 2581057, 2580168, 2579358, 0)
    println(partsSums(List(30350, 76431, 156228, 78043, 98977, 80169, 32457, 182875, 162323, 17508, 57971, 171907)))
   // List(1145239, 1114889, 1038458, 882230, 804187, 705210, 625041, 592584, 409709, 247386, 229878, 171907, 0)
  }

【讨论】:

    【解决方案2】:

    您不应该在递归函数中使用var 来传递状态。你可以这样修复你的方法:

    def partsSums(l: List[Int]): List[Int] = {
        if (l.isEmpty)
          Nil //to finish recursion return empty list
        else {
          l.sum :: partsSums(l.tail) //prepend newly calculated sum to list returned by recursive call
        }
    }
    

    但是这个解决方案很幼稚,因为它在每次迭代时重新计算总和。我们可以更好地从结果的头部获取先前的总和并通过将其添加到列表的头部来计算新的总和。我还使用另一个名为acc 的列表来累积结果,因为这样我可以使partsSums2 尾递归:

    def partsSums2(l: List[Int], acc: List[Int] = List(0)): List[Int] = {
        if(l.isEmpty) {
          acc //at the end of recursion we return acc, which holds result
        } else {
          //acc.head is previos sum and l.head is value I want to add
          partsSums2(l.tail, (l.head + acc.head) :: acc)
        }
    }
    

    为了让它工作,我们还需要在传递给方法之前反转列表:

    SumsOfParts.partsSums2(List(0, 1, 3, 6, 10).reverse)
    

    我们需要反转列表,因为 Scala 中不可变列表的实现对前置操作(O(1))非常有效,但对追加操作(O(n )).

    最后你可以使用scanRight:

    def partsSums3(l: List[Int]): List[Int] = l.scanRight(0)(_ + _)
    

    【讨论】:

    • 如果您将acc 的默认值设置为List(0),您可以使partsSums2 稍微干净一些。这样,head 应该始终存在,因此您不需要headOption。您可以使用cats.NonEmptyList 在编译时强制执行。
    • @Ethan 谢谢,已修复。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-05
    • 1970-01-01
    • 1970-01-01
    • 2022-10-06
    • 2020-04-06
    相关资源
    最近更新 更多