繁体   English   中英

Ruby:递归和执行顺序

[英]Ruby: Recursion and order of execution

以下方法的输出为[6,4,2,0,0,2,4,6]

我了解直到n-1处array = [6,4,2,0,0]为止的所有内容,并在行号添加了array [4] = 0。 3.但是我完全不明白为什么即使执行了第3行后该方法仍继续执行,然后应该返回[6,4,2,0,0]到原始方法调用。 更令人烦恼的是,n重置为n = 1,然后递增为n = 2,n = 3 ... n = 3是方法调用中传递的起始参数值。

此外,在遇到各种可能性的递归时,我遇到了主要问题。 对此问题的答案和关于“假人递归”的建议将不胜感激!

def append(array, n)
  return array if n < 0 #base case, how we end this thing
  array <<  n*2    #Line No. 1 

  append(array, n - 1) #Line No. 2 
  array <<  n*2    #Line No. 3

end
append( [], 3)

#output  [6,4,2,0,0,2,4,6]

这里的执行顺序没有什么神秘之处,而且您的计数器不会递增。 要了解会发生什么,让我们逐行浏览代码。 我将使用append([], 2)使其更快。

# you call append([], 2)
return array if n < 0
# n >= 0 so we continue
array <<  n*2
# array is now [4]
append(array, n - 1)
# you call append(array, 1) which will mutate array,
# lets call x what will be appended to it
# array is now [4, x]
array <<  n*2
# array is now [4, x, 4]
# you get [4, x, 4] as a returned value from the append method
# because after the first line there is no return statement,
# so the return value of the last line is returned

# let's now see what x, that is append(array, 1) is
return array if n < 0
# n >= 0 so we continue
array <<  n*2
# array is now [4, 2] because at that time, array is [4]
append(array, n - 1)
# you call append(array, 0) which will mutate array,
# lets call y what will be appended to it
# array is now [4, 2, y]
array <<  n*2
# array is now [4, 2, y, 2]
# this is what you return to the first method invocation
# so we can replace [4, x, 4] with [4, 2, y, 2, 4]

# let's now see what y, that is append(array, 0) is
return array if n < 0
# n >= 0 so we continue
array <<  n*2
# array is now [4, 2, 0] because at that time, array is [4, 2]
append(array, n - 1)
# you call append(array, -1) which will mutate array,
# lets call z what will be appended to it
# array is now [4, 2, 0, z]
array <<  n*2
# array is now [4, 2, 0, z, 0]
# this is what you return to the second method invocation
# so we can replace [4, 2, y, 2, 4] with [4, 2, 0, z, 0, 2, 4]

# now in the last invocation, z is nothing because -1 < 0,
# so nothing is appended to the array
# the first method invocation returns [4, 2, 0, 0, 2, 4]

return语句仅从其直接方法调用中返回。 方法是递归的事实并不会改变这一点。 它不会以某种方式找到自身的顶级调用并从中返回。

如果在递归时我可以给您建议,那就是不要改变您的论点。 使用纯函数更容易和直观,尤其是在这种情况下。 这就是您的append方法看起来没有突变的样子:

def append n
    return n < 0 ? [] : [n * 2, append(n - 1), n * 2].flatten
end

你会这样称呼它:

array = append(3)
# [6, 4, 2, 0, 0, 2, 4, 6]

这样,您的数组就不会发生变异,并且您可以清楚地看到该方法返回的结果。

如果您找不到更清晰的图像,请以这种方式可视化

# append(3)
[6,
    # append(2)
    [4,
        # append(1)
        [2, 
            # append(0)
            [0,
                # append(-1)
                []
            , 0].flatten
        , 2].flatten
    , 4].flatten
, 6].flatten

该方法被多次调用。 谈论“该方法如何继续执行”已经表明您没有错误地考虑这一点。

方法的每次调用都完全独立于其他每次调用,并且每个调用都具有唯一的n副本以及其自己的值。 该方法被调用四次,并且这四次调用中的每一次都将两个项目推入数组,从而产生总共八个项目。

理解正在发生的事情的关键是,每次方法调用都会将一个数字压入数组,然后调用自身,然后将另一个数字压入数组。 两个“ 6”的整体都由相同的方法调用推送,并且它们包装所有其他条目,因为递归发生方法为n为6的两个array << n*2调用之间

考虑以下:

def method_a
  puts "A start"
  method_b
  puts "A end"
end

def method_b
  puts " B start"
  method_c
  puts " B end"
end

def method_c
  puts "  C start"
  puts "  !!!"
  puts "  C end"
end

method_a

这不是递归的,但是其行为类似。 此代码的输出是:

A start
 B start
  C start
  !!!
  C end
 B end
A end

这与您似乎期望看到的相反:

A start
 B start
  C start
  !!!

每个函数输出一个“开始”行,调用其下一个函数,然后当嵌套调用返回时 ,它输出其“结束”行。 这正是递归函数的行为方式。 嵌套调用返回后,每个调用将继续执行。 n的局部值保持不变,并且第二次将其推入数组。

我认为您的想法是, return结束一切,但事实并非如此。

这是逐步发生的事情:

append(array = [], n = 3)                 # initial call
  array << 6                              #Line No. 1
  append(array = [6], n = 2)              #Line No. 2
    array << 4                            #Line No. 1
    append(array = [6,4], n = 1)          #Line No. 2
      array << 2                          #Line No. 1
      append(array = [6,4,2], n = 0)      #Line No. 2
        array << 0                        #Line No. 1
        append(array = [6,4,2,0], n = -1) #Line No. 2
          return array                    #base case
          # but `return` doesn't leave the recursion.
          # it only goes up one step in the call stack, like so:
        array << 0                        #Line No. 3 -> array = [6,4,2,0,0]
      array << 2                          #Line No. 3 -> array = [6,4,2,0,0,2]
    array << 4                            #Line No. 3 -> array = [6,4,2,0,0,2,4]
  array << 6                              #Line No. 3 -> array = [6,4,2,0,0,2,4,6]

我认为Line No. 3引起了一些混乱。 如果只是n*2 ,您将看到最后它没有返回array ,而是返回了Fixnum 这是针对这种情况的分步简化版本:

append([], 3)  # initial call
  array << 6; append([6], 2)
    array << 4; append([6,4], 1)
      array << 2; append([6,4,2], 0)
        array << 0; append([6,4,2,0], -1)
          return array
        0               # result of n*2 (Line No. 3)
      2                 # result of n*2 (Line No. 3)
    4                   # result of n*2 (Line No. 3)
  6                     # result of n*2 (Line No. 3)

#output = 6

另一方面,如果删除Line No. 3 ,则最后一行将是对append的调用的结果,这实际上与base case一致。

append([], 3)   # initial call
  array << 6; append([6], 2)
    array << 4; append([6,4], 1)
      array << 2; append([6,4,2], 0)
        array << 0; append([6,4,2,0], -1)
          return array
        array           # result from the call append([6,4,2], 0) (Line No. 2)
      array             # result from the call append([6,4], 1)   (Line No. 2)
    array               # result from the call append([6], 2)     (Line No. 2)
  array                 # result from the call append([], 3)      (Line No. 2)

#output = [6,4,2,0]

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM