Scala:列表上的递归求和

时间:2017-12-26 10:44:47

标签: scala recursion

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

任何人都可以解释最后一行。

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

3 个答案:

答案 0 :(得分:6)

解释递归的最佳方法恕我直言是通过逐步完成它来看看实际发生了什么。另一个有用的是添加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 call现在带有list(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((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 

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

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

答案 1 :(得分:1)

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

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

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

答案 2 :(得分:1)

两个中间结果(xs.head和sum(xs.tail))都存储在所谓的 frames 中,它们是执行线程Java堆栈中的内存区域。 为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。结果被推到操作数堆栈上。