【问题标题】:Why is this recursion NOT infinite?为什么这个递归不是无限的?
【发布时间】:2010-09-13 04:59:06
【问题描述】:

我和我的朋友们正在做一些基本的 Ruby 练习来感受这门语言,但我们遇到了一个有趣的行为,我们还无法理解。基本上,我们正在创建一个tree 数据类型,其中只有一个类node,它只包含一个值和一个零个或多个nodes 的数组。我们正在使用 rspec 的 autospec 测试运行器。有一次,我们开始编写测试以禁止无限递归(循环树结构)。

这是我们的测试:

it "breaks on a circular reference, which we will fix later" do
  tree1 = Node.new 1
  tree2 = Node.new 1
  tree2.add_child tree1
  tree1.add_child tree2
  (tree1 == tree2).should be_false
end

这是 Node 类:

class Node
  attr_accessor :value
  attr_reader :nodes

  def initialize initial_value = nil
    @value = initial_value
    @nodes = []
  end

  def add_child child
    @nodes.push child
    @nodes.sort! { |node1, node2| node1.value <=> node2.value }
  end

  def == node
    return (@value == node.value) && (@nodes == node.nodes)
  end
end

我们希望测试的最后一行导致无限递归,直到堆栈溢出,因为它应该不断地相互比较子节点并且永远不会找到叶节点。 (我们的印象是数组上的 == 运算符将遍历数组并根据 the array page of RubyDoc 对每个孩子调用 ==。)但是如果我们将 puts 扔进 ==方法来查看它被调用的频率,我们发现它恰好被调用了 3 次,然后测试通过了。

我们缺少什么?

编辑:请注意,如果我们将测试中的be_false 替换为be_true,则测试失败。所以它肯定认为数组不相等,它只是不递归它们(除了对== 的三个不同调用)。

【问题讨论】:

  • @beavis: "==" 在这个实现中应该是递归的,因为它试图通过比较它们的子节点来比较两个节点的子节点,这些节点作为子节点相互引用,依此类推。它是一个“树”,因为它是一个带有子节点的节点,子节点又可以有子节点等。见en.wikipedia.org/wiki/Tree_(computer_science)
  • 这是一棵相当凌乱的树。一个节点不应该知道如何在它所在的任何数据结构中安排自己。
  • @beavis:你指的是排序吗?这是我们想要添加以测试数组排序的东西,并且可能会随着代码的成熟而被删除。这是一个早期的原型,实际上不会用于任何事情。正如我所说,我们现在只是对这种语言有所了解。

标签: ruby recursion rspec


【解决方案1】:

如果单击您链接的 RubyDoc 的方法名称,您将看到 Array#== 方法的源代码(C 语言):

{
    // [...]
    if (RARRAY(ary1)->len != RARRAY(ary2)->len) return Qfalse;
    if (rb_inspecting_p(ary1)) return Qfalse;
    return rb_protect_inspect(recursive_equal, ary1, ary2);
}

此实现(特别是“recursive_equal”)表明Array#== 已经实现了您所追求的无限递归保护。

【讨论】:

  • 啊,确实如此。从技术上讲,我们所追求的保护是最终阻止它甚至在我们的数据类型上被允许,但这是 add_child 方法的东西。我想我们从来没有想过它会默默地返回而不抛出某种错误。我猜这都是习惯语言的一部分。出于好奇,你知道无声归来的原因吗? (诚​​然,我并不精通 C,所以我只是模糊地遵循了这个实现。)
  • 我的 C 也不是很好,但似乎上一行 if (rb_inspecting_p(ary1)) return Qfalse; 是实际触发“静默”错误返回的原因。基本上,如果我们在 inside ary1 的任何一点遇到 ary1,它似乎返回 false。
  • @Gareth:有道理。我什至没有注意到那些实现链接在那个网站上。仅此一项就可以帮助我们努力(以及让我们在阅读 C 方面有所提高)。知道检查的存在对我来说意味着检查很容易,这将在 add_child 方法中派上用场。
  • 好吧,请注意,并非 C API 中的所有内容都可以在 Ruby API 中使用 - Array 对象是在 C 中实现的,而不是作为纯 Ruby 对象。除非您将自己的代码编写为 C 扩展,否则您只需要进行 Ruby 调用即可。
  • 我是 Ruby 新手,但据我了解,最初的参考实现是用 C 语言编写的,但没有(现在仍然没有?)遵循规范。因此,在 Ruby 的另一个实现(例如 JRuby)上运行此代码是否可能会产生不同的结果(特别是无限递归)?还是我误解了整个“Ruby 的不同实现”的情况?
猜你喜欢
  • 1970-01-01
  • 2012-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-28
相关资源
最近更新 更多