Reputation: 2479
I am studying how to use Generators in Python in David Beazly's excellent Python Cookbook text. The following code recipe defines Depth First Tree Traversal using generators very elegantly:
# example.py
#
# Example of depth-first search using a generator
class Node:
def __init__(self, value):
self._value = value
self._children = []
def __repr__(self):
return 'Node({!r})'.format(self._value)
def add_child(self, node):
self._children.append(node)
def __iter__(self):
return iter(self._children)
def depth_first(self):
yield self
for c in self:
yield from c.depth_first()
# Example
if __name__ == '__main__':
root = Node(0)
child1 = Node(1)
child2 = Node(2)
root.add_child(child1)
root.add_child(child2)
child1.add_child(Node(3))
child1.add_child(Node(4))
child2.add_child(Node(5))
for ch in root.depth_first():
print(ch)
# Outputs: Node(0), Node(1), Node(3), Node(4), Node(2), Node(5)
I am trying to come up with an equally elegant method
def breadth_first(self):
pass
I am deliberately not posting the crazy stuff that I have been trying since everything that I have tried requires maintaining 'state' within it. I don't want to use the traditional queue based solutions. The whole point of this academic exercise is to learn how generators behave in depth. Therefore, I want to create a parallel 'breadth_first' method using generators for the tree above.
Any pointers/solutions are welcome.
Upvotes: 2
Views: 2759
Reputation: 97691
The depth first solution you implement is essentially "iterate then recurse"
def depth_first(self):
yield self
for c in self.children:
yield from c.depth_first():
Inspired by this blog post the activestate post it references, you obtain a breadth first search as "recurse then iterate":
def breadth_first(self):
yield self
for c in self.breadth_first():
if not c.children:
return # stop the recursion as soon as we hit a leaf
yield from c.children
Edit: it turns out that the linked blog says "Termination check is absent", replaced with an activestate link, which I've adapted to use yield from
above.
Upvotes: 0
Reputation: 5549
It would be easy if itertools
had:
# zip_chain('ABCD', 'xy') --> A x B y C D
It's almost a itertools.chain(itertools.zip_longest())
, but not quite.
Anyway, zip_chain
allows:
def bf(self):
yield self
yield from zip_chain(*map(Node.bf, self.children))
It doesn't create a whole row at a time either, I think,
Upvotes: 0
Reputation: 51
I find it both useful and elegant generating the whole breadth, one level at a time. Python 3 generator below:
def get_level(t: Node) -> Iterable[List]:
curr_level = [t] if t else []
while len(curr_level) > 0:
yield [node._value for node in curr_level]
curr_level = [child for parent in curr_level
for child in parent._children
if child]
Upvotes: 0
Reputation: 30288
You can't use recursion (stack) for bfs
without some serious hacks, but a queue would work:
def breadth_first(self):
q = [self]
while q:
n = q.pop(0)
yield n
for c in n._children:
q.append(c)
Upvotes: 7