Python为了遍历一个平面列表

Posted

技术标签:

【中文标题】Python为了遍历一个平面列表【英文标题】:Python in order traversal to a flat list 【发布时间】:2012-03-14 11:07:33 【问题描述】:

我创建了一个 TreeNode 类的方法,我想要返回一个按顺序遍历树的平面列表

我的示例树是:

按顺序遍历输出应该是:[1, 1, 0, 2, 1, 3, 1, 1, 0]

但我得到:[2, 1, 1, 0, 1, 3, 1, 1, 0]

这是我的代码:

def in_order_list(self, r = []):
    hasLeft = self.left is not None
    hasRight = self.right is not None
    if self is None:
        return
    else:
        if hasLeft:
            self.left.in_order_list(r)
        r.append(self.value)
        if hasRight:
            self.right.in_order_list(r)
    return r

谁能告诉我为什么会这样?

谢谢 亚历克斯

【问题讨论】:

预购清单在哪里定义。图的数据结构是什么? 【参考方案1】:

您不是调用self.left/right.in_order_list(),而是调用self.left/right.pre_order_list()

为了完成你想做的事情,生成器函数可能比在列表中累积值更好(更少的内存消耗和更多的pythonic):

class Tree(object):
    ...
    def in_order(self):
        if self.left is not None:
            for value in self.left.in_order():
                yield value
        yield self.value  #  <-- yielding the value of the node, not the node itself
        if self.right is not None:
            for value in self.right.in_order():
                yield value

...

tree = Tree(...)

in_order_values = list(tree.in_order())

这样,如果您只想迭代值,则不必创建列表:

for value in tree.in_order():
    ...

算法的工作原理是这样的:生成器首先沿着每个节点的左分支递归下降,直到它遇到一个没有左子节点的分支。然后它产生当前节点的值。之后,它在正确的子节点上执行相同的操作,但从当前节点开始,而不是从根节点开始。如果我们假设树中没有循环并且没有无限分支,那么肯定会有叶子节点,即没有左子节点或右子节点的节点。 IOW 节点,两种基本情况 (self.left/right is None) 均已达到。因此,递归调用将返回,希望在内存不足或达到堆栈限制之前返回。

self.left/right.in_order() 上的循环是必要的,因为对 in_order() 的调用返回的是生成器,因此名称为 生成器函数。返回的生成器必须以某种方式耗尽,例如通过一个循环。在循环的主体中,我们将值重新生成一个级别,然后再次重新生成,直到它们达到***别。我们在那里使用这些值。

如果您想自己检索节点而不是仅检索它们的值字段,您可以这样做:

class Tree(object):
    ...
    def in_order(self):
        if self.left is not None:
            for node in self.left.in_order():
                yield node
        yield self  #  <-- yielding the node itself
        if self.right is not None:
            for node in self.right.in_order():
                yield node

您可能想要这样做,因为您不仅可以访问节点的值:

for node in tree.in_order():
    do_something_with(node.value)

但你也可以对节点做任何你想做的事情:

for node in tree.in_order():
    whatever(node.some_other_attribute)

【讨论】:

左右yield的生成器是节点的值还是节点对象? 试图了解正在发生的事情,我会说 for leftfor right 递归调用他们 yield node object - 对吗?【参考方案2】:

你可以把这种东西写成一个生成器,并且避免处理列表和追加:

 def in_order(tree):
    if tree is None: return

    for value in in_order(tree.left): yield value
    yield tree.value
    for value in in_order(tree.right): yield value

例如:

>>> class Node(object):
...     def __init__(self, value, left=None, right=None):
...             self.value, self.left, self.right = value, left, right
... 
>>> x = Node(3)
>>> x.left = Node(2)
>>> x.left.left = Node(1)
>>> x.left.left.left = Node(1)
>>> x.left.left.right = Node(0)
>>> x.left.right = Node(1)
>>> x.right = Node(1)
>>> x.right.left = Node(1)
>>> x.right.right = Node(0)
>>> 
>>> in_order(x)
<generator object in_order at 0xb742dbbc>
>>> list(in_order(x))
[1, 1, 0, 2, 1, 3, 1, 1, 0]

【讨论】:

感谢您使用 generator 阅读的解决方案,看起来生成器负责处理调用之间的局部变量。虽然使用 generator 很强大,但你会说它是这样的最经典的实现吗?虽然它非常优雅。谢谢【参考方案3】:

r = [] 的默认参数可能有问题

例子:

def foo(list=[]):
    list.append(1)
    print list


foo()
>>> [1]

foo()
>>> [1,1]

Python 在多个函数调用中保持相同的列表。

试着让你的函数开始像这样:

def in_order_list(self, r=None):
    if r is None:
        r = []

您可能想发布其余代码,以便我们知道 pre_order 函数的作用。

【讨论】:

嗨@Matt 和@campos,你们都问我pre_order 函数的作用突出了我的错误!该函数应自称in_order_list 而不是pre_order 函数。这个方法现在有效!感谢@campos.ddc 对list 多次调用的洞察。【参考方案4】:

A) 首先如 campos.ddc 所述,将 [] 赋值给 r 参数是有问题的。有关此问题的详细信息,请咨询this answer on ***(这是一个非常常见的错误)

B) 看起来你的“如果 self 是 None:”测试是多余的,如果 self 是 None,你将无法调用 in_order_list 方法(假设这是一个类中的方法,而不是独立的函数)

C) 代码可以简化:

def in_order_list(self, r = None):
    if not r:
        r = []
    if self.left:
        self.left.in_order_list(r)
    r.append(self.value)
    if self.right:
        self.right.in_order_list(r)

D) 可能是“家庭作业”问题的问题应该这样标记。 >:(

【讨论】:

使用if r is None 而不是if not r 来测试Noneness——否则你可能会感到困惑,例如0。 (这在这种特殊情况下无关紧要,但一般来说是一个很好的规则。) 我不同意,期望 r 是一个列表,所以它可以被评估为 False 的唯一方法是如果 A) 它是 None,或者 B) 是空的。包含值 0 的列表将评估为 True,如果 r 是值 0,则表示调用者的错误。在任何一种情况下,if not r 都比if r is None: 更简洁易读(恕我直言) 是的,但我的观点是,为了与单身人士进行比较,您应该使用is。对我来说,if not r 立即让我想到非[] boolean-False 值。 换句话说,if r is None 测试上面的值,所以很明显,它成功的唯一方法是不覆盖默认参数。 if not r 不会针对该值进行测试,因此您必须仔细考虑案例才能确定它是否正确。 "所以很明显,它成功的唯一方法是不覆盖默认参数" - 或者用户传入None。我们在这里分裂头发,我真的不认为这是特别对还是错。

以上是关于Python为了遍历一个平面列表的主要内容,如果未能解决你的问题,请参考以下文章

Python列表遍历

python_列表_循环遍历

python 从列表列表中制作一个平面列表

平面列表

从父/子的平面列表构建层次结构对象

Python中的迭代遍历 for in