ruby 有没有一个好的方法来判断一个方法是否已经递归了?

doinxwow  于 2023-06-29  发布在  Ruby
关注(0)|答案(2)|浏览(139)

我在写递归函数。如果有一种好的方法来判断方法的当前运行是否在递归堆栈中,那就太好了。就像这样:

def mymethod
  if yourcodehere
    # do stuff
  end
  
  mymethod()
end

有什么想法吗?如果有好的方法,我会为它写一个Ruby宝石。

fxnxkyjh

fxnxkyjh1#

您可以使用caller_locations

def mymethod(x)
  first, *stack = caller_locations(0)
  recursion = stack.any? do |cl|
    cl.absolute_path == first.absolute_path && cl.label == first.label
  end

  p x: x, recursion: recursion

  mymethod(x + 1) if x < 3
end

mymethod(1)
# {:x=>1, :recursion=>false}
# {:x=>2, :recursion=>true}
# {:x=>3, :recursion=>true}

通过传递0start 值,caller_locations将返回自己的调用,您可以将其与剩余的调用者位置进行比较以检测递归,即甚至跨多个方法调用。您可以通过传递第二个 limit 参数来限制条目的数量,例如如果您只需要检测直接调用。
请注意,在启用tail call优化时,上述方法不起作用,因为它有效地重用了最后一个堆栈帧,这使得每个递归调用看起来都像初始调用。
一种更简单的方法是在从自身调用方法时只传递一个可选参数,以显式地指示递归调用,例如:

def mymethod(x, recursion: false)
  p x: x, recursion: recursion

  mymethod(x + 1, recursion: true) if x < 3
end

mymethod(1)
# {:x=>1, :recursion=>false}
# {:x=>2, :recursion=>true}
# {:x=>3, :recursion=>true}

除了调试或检测(无意的)无限循环之外,我会避免让一个方法根据其调用上下文有不同的行为。

kfgdxczn

kfgdxczn2#

方法名匹配调用方

有不止一种方法可以完成您想要的任务,但是确定调用方是否是当前方法是微不足道的。例如,假设原始性能不是您的目标,那么下面的代码就可以正常工作。

def foo
  puts caller.first.match?(__method__.to_s) ? "tail call" : "first call"
  sleep 0.1
  foo
end

这将返回:
第一呼叫
尾唿叫
尾唿叫
尾唿叫
...
因为只有当Kernel#caller包含由Kernel#__method__标识的当前方法时,三元条件才为真。

向尾部调用传递布尔参数

我不确定当你的堆栈变得更深时,上面的方法是否会有性能损失。如果发生这种情况,您可以尝试将显式的 startlength 参数传递给Kernel#caller,或者只是将布尔参数作为tail调用的一部分传递。例如:

def foo recursion=false
  puts recursion ? "tail call" : "first call"
  sleep 0.1
  foo recursion: true
end

这给出了与第一个示例完全相同的结果,但不再需要对堆栈跟踪执行正则表达式匹配或对当前方法的Symbol进行自检。它只是在进行尾调用时传递一个truthy值作为关键字参数,因此这种方法可能更快地进行评估。但是,我并没有费心对这两种方法进行基准测试,因此您的里程可能会有所不同。

相关问题