Bharat
Bharat

Reputation: 2479

Breadth First Tree Traversal using Generators in Python

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

Answers (4)

Eric
Eric

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

Him
Him

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

kush
kush

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

AChampion
AChampion

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

Related Questions