我正在遵循《数据结构和算法》一书中的BinarySearchTree代码。 您想阅读此链接中的完整代码吗?
而且我不清楚这种方法是如何工作的
def __iter__(self):
if self.left != None:
for elem in self.left:
yield elem
yield self.val
if self.right != None:
for elem in self.right:
yield elem
elem变量是Node类的实例还是浮点数(来自输入)?在调试中两者兼而有之,我想这个值因为行产量而改变,但我不明白。
yieldelem和yield self.val有什么区别?在这种情况下有多少个生成器对象?
另外,您想分享一些调试生成器函数的经验吗?调试时我对产量感到困惑。
1.
elem
是一个Node
实例。从 for 循环中,我们知道elem
总是self.left
或self.right
.您可以在示例用法中看到,浮点值与tree.insert(float(x))
一起插入到二叉树中,BinarySearchTree.insert()
方法最终调用BinarySearchTree.Node(val)
在这种情况下val
float(x)
。因此,self.left
和self.right
始终是Node
实例。
-
正如评论中提到的,不要只谈论代码,
elem
是一个浮动。我以前没有看到这个,因为我认为迭代self.left
会产生一个Node
元素的列表。但是,这是不正确的。实际上,在这种情况下,通过调用self.left.__iter__()
来迭代self.left
工作。我将这个__iter__()
函数分解为 3 种情况,几乎就像递归函数一样。(它实际上不是递归的,因为它调用Node
类的不同实例的__iter__()
方法,但它的行为是相似的。- 首先,节点没有
left
或right
子节点。这很简单:迭代器只会产生self.val
,这是一个浮点数。 - 其次,节点有
left
子节点。在这种情况下,for 循环将以几乎递归的方式向下遍历所有剩余的子节点,直到到达没有left
子节点。然后我们回到第一种情况。 - 第三,节点有
right
子节点。在这种情况下,在返回自己的节点self.val
后,迭代器将继续到第一个right
节点,并重复。
- 首先,节点没有
-
只有一个生成器,即
Node.__iter__()
,因为生成器是函数。它使用多个yield
语句根据情况返回不同的值。yield elem
和yield self.val
如果当前节点具有左分支或右分支或当前节点的值,则只返回Node
。 -
我没有特别调试
yield
语句的具体技巧。通常,我在构建代码时使用IPython进行交互式工作,并使用其内置的%debug
魔术运算符。您可能还会发现橡皮鸭调试很有用。
使用 IPython,您可以在单元格中运行以下命令以交互方式进行调试。
In [37]: %%debug
...: for x in tree.root:
...: print(x)
...:
NOTE: Enter 'c' at the ipdb> prompt to continue execution.
然后,可以在调试器提示符下使用s
命令,ipdb>
单步执行代码,跳转到函数调用。
ipdb> s
--Call--
> <ipython-input-1-c4e297595467>(30)__iter__()
28 # of the nodes of the tree yielding all the values. In this way, we get
29 # the values in ascending order.
---> 30 def __iter__(self):
31 if self.left != None:
32 for elem in self.left:
调试时,可以通过在表达式前面加上感叹号!
来计算表达式。
ipdb> !self
BinarySearchTree.Node(5.5,BinarySearchTree.Node(4.4,BinarySearchTree.Node(3.3,BinarySearchTree.Node(2.2,BinarySearchTree
.Node(1.1,None,None),None),None),None),None)
首先,您共享的代码中存在缩进问题:yield self.val
不应该在if
块中:
def __iter__(self):
if self.left != None:
for elem in self.left:
yield elem
yield self.val # Unconditional. This is also the base case
if self.right != None:
for elem in self.right:
yield elem
要理解此代码,首先开始想象一个只有一个节点的树。让我们暂时忽略BinarySearchTree
类,并说我们可以直接访问Node
类。我们可以创建一个节点,然后迭代它:
node = Node(1)
for value in node:
print(value)
此循环将调用__iter__
方法,在这种情况下,该方法不会执行任何if
块,因为它没有子块,并且只执行yield self.val
。这就是value
在上面的循环中将获得的值,并被打印出来。
现在用另外 2 个节点扩展这个小练习:
node = Node(1,
Node(0),
Node(2)
)
for value in node:
print(value)
在这里,我们创建了这棵树,node
指的是它的根
1 <-- node
/
0 2
当for..in
循环现在调用__iter__
时,它将首先进入第一个if
块,在那里我们得到一种递归形式。有了for
语句,我们再次执行__iter__
,但这次是在node
的左子节点,即值为 0 的节点。但这是我们已经知道的情况:这个节点没有子节点,我们从上面的第一个例子中知道,这会导致一次迭代,其中循环变量将是该节点的值,即 0,并且生成该值。这意味着主程序获得value
等于 0 的迭代,该迭代被打印出来。
所以elem
是数字。最好将其称为value
或val
以消除任何混乱。
在那之后if
块执行,我们就可以yield self.val
了。self
在这里node
,所以我们得到1。这意味着主程序可以执行第二次迭代,这次value
等于 1。
最后执行第二个if
块,现在node
的右子块是递归__iter__
调用的主题。这与左孩子的原理相同。这将产生值 2,主程序打印值 2。
我们可以再次用更多的节点扩展树,但原理是相同的:通过递归调用__iter__
,产生树的所有值。
yield from
有一种语法可以简化代码,并且在与None
进行比较时,使用is
运算符也是更常见的做法:
def __iter__(self):
if self.left is not None:
yield from self.left
yield self.val
if self.right is not None:
yield from self.right
这会导致相同的行为。yield from
将生成来自可迭代对象的所有值。由于节点实例具有__iter__
方法,因此可以迭代,因此可以按预期工作。