【问题标题】:Scala: Recursive Summation on ListScala:列表上的递归求和
【发布时间】:2017-12-26 10:44:47
【问题描述】:
def sum(xs: List[Int]): Int = {
  if(xs.isEmpty)
    0
  else
    xs.head + sum(xs.tail)
}

谁能解释一下最后一行。

那么中间结果存储在哪里 xs.head + sum(xs.tail) 在 + 之后是否提供要添加的单个元素?

【问题讨论】:

  • 您的问题不清楚。你在说什么“中间结果”? “提供要添加的单个元素”是什么意思?特别是,您能否准确地定义“提供”和“添加”的含义?
  • sum of a list = first element of list + sum of rest of the list

标签: scala recursion


【解决方案1】:

恕我直言,解释递归的最佳方法是逐步完成它,看看实际发生了什么。另一件有帮助的事情是添加 return 语句,尤其是如果您来自 Java 之类的语言,因为它更容易理解正在发生的事情。

您的返回函数如下所示:

def sum(xs: List[Int]): Int = {
  if(xs.isEmpty)
    return 0
  else
    return xs.head + sum(xs.tail)
}

在您的情况下,您有一个函数可以对列表中的所有整数求和。

所以让我们想象一下,您使用具有以下值 (1,2,3) 的列表调用了函数

函数将如何表现?

第一个函数调用如下所示:

if(xs.isEmpty) // false - it has 3 elements (1,2,3)
   return 0 // skipped
else
   return 1 + sum((2,3)) // xs.head is changed with 1 and xs.tail is list with (2,3)

第二次调用现在使用列表 (2,3):

if(xs.isEmpty) // false - it has 2 elements (2,3)
   return 0 // skipped
else
   return 2 + sum((3)) // xs.head is changed with 2 and xs.tail is list with (3)

Trird 调用现在使用列表 (3):

if(xs.isEmpty) // false - it has 1 elements (3)
   return 0 // skipped
else
   return 3 + sum(()) // xs.head is changed with 3 and xs.tail is empty list now

第四次调用是空列表:

 if(xs.isEmpty) // true
    return 0 // Recursion termination case triggered

所以现在我们的 sum 调用堆栈看起来像这样:

sum((1,2,3))   
   where sum = 1 + sum((2,3))   
       where sum = 2 + sum((3))   
          where sum = 3 + sum(())    
             where sum = 0

我们只是开始返回值:

sum((1,2,3))   
       where sum = 1 + 5   
           where sum = 2 + 3  
              where sum = 3 + 0 

所以我们最终得到 sum((1,2,3)) = 6

这就是我们不需要存储“中间结果”的原因,因为计算总和从末尾开始并向后滚动。

【讨论】:

    【解决方案2】:

    根据实际示例进行思考可能会对您有所帮助。假设我们有:

    List(1,3,5)
    

    将此传递给sum 方法,第一个测试将失败(列表不为空)。然后它将头部项目(即1)添加到尾部的sum,即sum(List(3,5))。因此,在计算第二个表达式并再次调用 sum 之前,操作无法完成。初始测试失败(List(3,5) 不为空),该方法返回值3 + sum(List(5))。在计算第二个表达式之前它无法完成,因此再次调用sum。再一次,初始测试失败,因为List(5) 不为空,并且此调用返回值5 + sum(List())。最后一次调用sum,这次初测成功,返回0,所以:

    sum(List()) = 0
    sum(List(5)) = 5
    sum(List(3,5)) = 8
    sum(List(1,3,5)) = 9
    

    弄清楚这类事情对于理解递归很有用(也是必不可少的)。

    【讨论】:

      【解决方案3】:

      两个中间结果(xs.head 和 sum(xs.tail))都存储在所谓的 frames 中,它们是执行线程的 Java 堆栈中的内存区域。 为 sum 函数的每个嵌套调用创建一个单独的框架,因此这些中间结果对于每个 sum 调用都是独立的。

      来自Java documentation

      框架用于存储数据和部分结果,以及执行动态链接、方法返回值和调度异常。

      每次调用方法时都会创建一个新框架。框架在其方法调用完成时被销毁,无论该完成是正常的还是突然的(它会引发未捕获的异常)。帧是从创建帧的线程的 Java 虚拟机堆栈(第 2.5.2 节)分配的。每个帧都有自己的局部变量数组(第 2.6.1 节)、自己的操作数堆栈(第 2.6.2 节)以及对当前方法类的运行时常量池的引用(第 2.5.5 节) .

      以下是您的代码如何编译成 JVM 字节码:

        public int sum(scala.collection.immutable.List<java.lang.Object>);
          Code:
             0: aload_1
             1: invokevirtual #63                 // Method scala/collection/immutable/List.isEmpty:()Z
             4: ifeq          11
             7: iconst_0
             8: goto          30
            11: aload_1
            12: invokevirtual #67                 // Method scala/collection/immutable/List.head:()Ljava/lang/Object;
            15: invokestatic  #73                 // Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
            18: aload_0
            19: aload_1
            20: invokevirtual #76                 // Method scala/collection/immutable/List.tail:()Ljava/lang/Object;
            23: checkcast     #59                 // class scala/collection/immutable/List
            26: invokevirtual #78                 // Method sum:(Lscala/collection/immutable/List;)I
            29: iadd
            30: ireturn
      

      注意接近末尾的 iadd 指令。来自 iadd 指令的description

      value1 和 value2 都必须是 int 类型。 值从操作数堆栈中弹出。 int 结果是 value1 + value2。结果被压入操作数堆栈。

      【讨论】:

        猜你喜欢
        • 2016-04-30
        • 1970-01-01
        • 2011-09-17
        • 1970-01-01
        • 2018-12-02
        • 2014-07-31
        • 1970-01-01
        • 2016-05-23
        • 2017-01-13
        相关资源
        最近更新 更多