递归和 Kernel#rand (Ruby) 的奇怪行为

Strange behavior with recursion and Kernel#rand (Ruby)

我在Ruby中有这样的方法:

def guess
  random_guess = rand(4)
  if random_guess == 0
    guess
  end

  puts random_guess
end
guess

如你所见,它的目的是生成一个介于0和4之间的随机数,然后如果该数字为0则重复。这个想法是为了防止该方法打印出0。但是,它似乎并没有出于某种奇怪的原因做出这种行为。例如,这里是 IRB 中的一个例子,当 random_guess 得到一个值 0:

2.2.0 :001 > def guess
2.2.0 :002?>     random_guess = rand(4)
2.2.0 :003?>     if random_guess == 0
2.2.0 :004?>         guess
2.2.0 :005?>       end
2.2.0 :006?>     
2.2.0 :007 >       puts random_guess
2.2.0 :008?>   end
 => :guess 
2.2.0 :009 > guess
2
0
0
0
0
 => nil 
2.2.0 :010 > 

当它没有得到值 0 时,程序正常运行并简单地打印出该范围内的任何其他随机数:

2.2.0 :010 > guess
1
 => nil 
2.2.0 :011 > 

我想知道的是为什么程序在 random_guess 为 0 时发生的递归行为如此奇怪。看起来它是重复出现的,但它似乎没有在正确的条件下停止,即当 random_guess 不为 0 时。为什么会这样?

random_guess0 时,guess 递归调用自身,这是正常工作的。您看到的输出是因为 puts random_guess 之后 执行了递归。因此,当您看到输出为,例如:

3
0
  1. guess 是这样调用的:random_guess 得到 0,所以调用 guess.
  2. 里面guess,random_guess得到3,把 当前 random_guess,即 3。退出 inner guess
  3. random_guess的值放入outterguess,即 0,然后退出。

它完全按照您的指示进行操作。如果你得到一个非零,你不输入 if-子句,所以你立即打印非零并完成。

但是,如果您生成的第一个值为零,则您进入递归并重复该过程,直到生成一个非零值。只有这样你才能绕过 if 来遇到打印语句和递归中的 return 。由于仅当您有零时才调用递归,因此您现在打印该零和 return.

最终结果,输出将是一个非零值,然后是出现的所有零,以相反的顺序弹出堆栈。

因为它recursive。在维护每个剩余指令的堆栈中。

假设如果出现 0,它会在堆栈中添加 puts random_guess 行。在完成递归 guess 调用后,它会弹出剩余的指令。

所以它打印 0 直到 guess 的许多递归调用。

# closer to your original design
# fyi, returns values 1 - 3
def guess
  random_guess = 0
  while random_guess == 0
    random_guess = get_random_guess(4)
  end

  random_guess
end

def get_random_guess(max_guess)
    rand(4)
end

# test guess 100 times
(1..100).each { puts "guess=#{guess}" }



# faster, note the use of rand without a max parameter. 
# in this case, rand returns a float between 0 and 1
# this return values between min_guess and max_guess
def faster_guess(min_guess, max_guess)
  (rand() * (max_guess + 1 - min_guess)).to_i + min_guess
end

# test faster_guess 100 times
# will return values between 1 - 4
(1..100).each { puts "faster_guess=#{faster_guess(1,4)}" }

因为您将 puts 语句放在您的方法中,它会在每次调用时打印出随机猜测的值,无论它是否是 0。递归调用自身的方法只会暂时中断该点的控制流,直到它移回堆栈。

您想要做的是使用方法 return 第一个不是 0 的值。示例:

def guess
  random_guess = rand(4)
  (random_guess > 0) ? random_guess : guess
end

puts guess
# 1 (never 0)

在此修改后的方法中,如果条件大于 0,则条件将 return random_guess。如果不满足,就会递归调用自己,直到满足条件。

请注意,递归的这种用法理论上 创造了无限递归的可能性,尽管这在实践中永远不会发生,但该方法的执行时间将是随机的。