Converting recursive to iterative in python

Question:

I just wrote a basic program that performs the Collatz Conjecture. However, I’m running into a stack overflow because I’ve written the program recursively, instead of iterating (according to other questions on this website). There are some other answers to this question, but I’m unsure of how to implement them in my own program. The simplified version is as below: (edit: I just translated some code to make it simpler, and I actually have a problem running this, but you get the idea of what’s happening – pastebin of actual code)

def collatz(number):
    if number == 1:
        print('Finished!')

    if number % 2 == 0:
        number = number/2
        collatz(number)
    elif number % 2 != 0:
        number = (number*3) + 1
        collatz(number)

n = int(input('Pick a number'))
collatz(n)

This seems fairly simple, but I can’t really seem to understand this. I tried a simpler example:

def fun(n):
    while n < 10:
        n += 1
        fun(n)

fun(1)

I tried to convert this by iterating by the examples I’ve seen on this site

def fun(n):
    while n < 10:
        n += 1
        return n

fun(1)

But I am unsure of how to implement the return of n to iterate the program.

Asked By: Alexander Lozada

||

Answers:

Generally, your latter two examples would be written:

def recr_fun(n):
    if n >= 10:
        return n
    return recr_fun(n+1)

def iter_fun(n):
    while n < 10:
        n += 1
    return n # note identation

Note that the recursive version has to pass the result back up through the recursive calls, and the iterative version returns after the loop has finished.

Answered By: jonrsharpe

You’re right, a while loop would work just fine here:

def collatz(number):
    number = int(number)
    while number != 1:
        if number % 2 == 0:
            number = number/2
        elif number % 2 != 0:  # could be just `else` -- either it's divisible by 2 or it isn't :)
            number = (number*3) + 1
    print("finished")

n = int(input('Pick a number'))
collatz(n)

In this case, the non-recursive formalism actually is a lot cleaner since your function doesn’t actually return anything.

Answered By: mgilson

You get a stack overflow because the programm is incorrect:

def collatz(number):
    if number == 1:
        print('Finished!')
    elif number % 2 == 0:
        number = number/2
        collatz(number)
    elif number % 2 != 0:
        number = (number*3) + 1
        collatz(number)

this work fine without stack overflow. However an iterative version of your code is

def iterCollatz(number):
    while number != 1:
        if number % 2 == 0:
            number = number / 2
        else:
            number = (number * 3) + 1
    print('Finished!')
Answered By: Salvatore Avanzo

You have forgotten to return when it finishes:

def collatz(number):
    if number == 1:
        print('Finished!')
        return # you are causing an infinite loop if you dont do this

    if number % 2 == 0:
        number = number/2
        collatz(number)
    elif number % 2 != 0:
        number = (number*3) + 1
        collatz(number)

You can clean up the code significantly like so:

def collatz(number):
    if number == 1:
        print('Finished!')
        return # you are causing an infinite loop if you dont do this

    if number % 2 == 0:  collatz(number/2)
    else:                collatz((number*3) + 1)

Cheers!

Answered By: ssm

In most cases there’s no way to algorithmically say “If I perform these operations on this recursive function, it changes to iterative.” Instead you need to look at the whole of the program and see how it works, then understand how you can make it work without recursion. For instance, let’s write a recursive solution to flatten a list (containing lists as well as numbers and etc)

def recurse_flatten(target):
    """recurse_flatten(list_of_lists) -> flat list"""
    accumulator = list()
    for element in target:
        if hasattr(element,"__iter__"):
            accumulator.extend(recurse_flatten(element))
        else:
            accumulator.append(element)
    return accumulator

As you can see, the function calls itself if the top-level element has the attribute "__iter__", which just means that it’s iterable. Instead, we could do this:

def iter_flatten(target):
    """iter_flatten(list_of_lists) -> flat list"""
    depth = 0
    targets = [iter(target)]
    accumulator = list()
    while True:
        try:
            element = next(targets[depth])
        except StopIteration:
            if depth == 0: return accumulator
            else:
                depth -= 1
                targets.pop()
        else:
            if hasattr(element,"__iter__"):
                targets.append(iter(element))
                depth += 1
            else: accumulator.append(element)

As you can see, the iterative version is much longer and more verbose, but it never nests. That can be a good thing if you have limited stack space, but generally (at least if you’re writing Python) the more readable the code is, the better it is and let me tell you right now that the recursive code is much MORE READABLE. Timing wise, you’ll have to test it yourself. On MY system:

# TEST DATA: [1, 2, [3, 4, 5, 6], 7, [8, [9, 10, 11]]]

# timeit.timeit(recurse_flatten)
10.985460426787078
# timeit.timeit(iter_flatten)
16.635406831330158
Answered By: Adam Smith

The solution is:

steps = 0
 
def collatz(number):
    global steps
 
    while number != 1:
        #special cases
 
        if number == 0:
            print('Enter a non-zero number.')
            inp()
        elif number < 0:
            print('nYou entered a negative number.  Solving for the absolute value instead.n')
            collatz(abs(number))
 
 
        #general solving
        if number % 2 == 0 and number != 1:
            steps += 1
            print (str(number) + ' is even.  Dividing by 2')
            number = int(number/2)
        elif number % 2 != 0 and number != 1:
            steps += 1
            print (str(number) + ' is odd.  Multiplying by 3 and adding one')
            number = int((number*3) + 1)
 
    else:
        print ('nFinished in %s stepsn' % steps)
        steps = 0
        inp()
 
 
def inp():
    n = input('Any number: ')
    if n.isdigit():
        collatz(int(n))
    else:
        print ('Enter a number!')
        inp()
 
inp()

This answer was posted as an edit to the question Converting recursive to iterative in python by the OP Alexander Lozada under CC BY-SA 3.0.

Answered By: vvvvv
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.