Recursion using yield

42,760

Solution 1

Yes, you can do this:

def infinity(start):
    yield start
    for x in infinity(start + 1):
        yield x

This will error out once the maximum recursion depth is reached, though.

Starting from Python 3.3, you'll be able to use

def infinity(start):
    yield start
    yield from infinity(start + 1)

If you just call your generator function recursively without looping over it or yield from-ing it, all you do is build a new generator, without actually running the function body or yielding anything.

See PEP 380 for further details.

Solution 2

In some cases it might be preferable to use a stack instead of recursion for generators. It should be possible to rewrite a recursive method using a stack and a while loop.

Here's an example of a recursive method which uses a callback and can be rewritten using stack logic:

def traverse_tree(callback):
    # Get the root node from somewhere.
    root = get_root_node()
    def recurse(node):
        callback(node)
        for child in node.get('children', []):
            recurse(child)
    recurse(root)

The above method traverses a node tree where each node has a children array which may contain child nodes. As each node is encountered, the callback is issued and the current node is passed to it.

The method could be used this way, printing out some property on each node.

def callback(node):
    print(node['id'])
traverse_tree(callback)

Use a stack instead and write the traversal method as a generator

# A stack-based alternative to the traverse_tree method above.
def iternodes():
    stack = [get_root_node()]
    while stack:
        node = stack.pop()
        yield node
        for child in reversed(node.get('children', [])):
            stack.append(child)

(Note that if you want the same traversal order as originally, you need to reverse the order of children because the first child appended to the stack will be the last one popped.)

Now you can get the same behavior as traverse_tree above, but with a generator:

for node in iternodes():
    print(node['id'])

This isn't a one-size-fits-all solution but for some generators you might get a nice result substituting stack processing for recursion.

Solution 3

def lprint(a):
    if isinstance(a, list):
        for i in a:
            yield from lprint(i)
    else:
        yield a

b = [[1, [2, 3], 4], [5, 6, [7, 8, [9]]]]
for i in lprint(b):
    print(i)
Share:
42,760
juliomalegria
Author by

juliomalegria

Me: juliomalegria.com Currently based in Antwerp, BE. Worked as an SRE at Google (San Francisco office), and as a Software Engineer at YouTube (Paris office). Studied Computer Science at UCSP.

Updated on July 27, 2020

Comments

  • juliomalegria
    juliomalegria almost 4 years

    Is there any way to mix recursion and the yield statement? For instance, a infinite number generator (using recursion) would be something like:

    def infinity(start):
        yield start
        # recursion here ...
    
    >>> it = infinity(1)
    >>> next(it)
    1
    >>> next(it)
    2
    

    I tried:

    def infinity(start):
        yield start
        infinity(start + 1)
    

    and

    def infinity(start):
        yield start
        yield infinity(start + 1)
    

    But none of them did what I want, the first one stopped after it yielded start and the second one yielded start, then the generator and then stopped.

    NOTE: Please, I know you can do this using a while-loop:

    def infinity(start):
        while True:
            yield start
            start += 1
    

    I just want to know if this can be done recursively.

  • Jo So
    Jo So about 8 years
    But it seems with yield from there is still a recursion limit :(
  • 00prometheus
    00prometheus over 6 years
    Nice answer! Yield in python 2.7 can not really be used with recursion, but by manually managing the stack you can get the same effect.
  • Coffee_Table
    Coffee_Table over 5 years
    although this answer requires more detail, it is actually in line with Sven Marnach's accepted answer, see his first bit of code...
  • Tomerikoo
    Tomerikoo almost 5 years
    What is b? Try not to leave code-only answers... A little clarification and explanation will help to put things in context and better understand your answer
  • Admin
    Admin almost 5 years
    for i in lprint(a): print(i)
  • Tomerikoo
    Tomerikoo almost 5 years
    Why not edit the answer so it is more clear? You can do it by clicking the little edit tag below your answer or click here. Also, as I said try to add a little explanation for how and why this solves the problem
  • Radio Controlled
    Radio Controlled over 4 years
    there will always be a recursion limit