1

Although this may seem like a duplicate (maybe it is but I haven't found a solution to this version of the problem yet), I don't think it is.

Below is my recursive function which blows python's recursion limit and I'd need to make it iterative instead but I'm having issues seeing how it could be possible.

def countChain(n, cache):
    if cache[n] != -1:
        return cache[n]

    if n % 2 == 0:
        cache[n] = 1 + countChain(n / 2, cache)
    else:
        cache[n] = 2 + countChain((3 * n + 1) / 2, cache)

    return cache[n]

Note that here my cache list has 1 million elements in it... (which is why the recursion kills python). I've seen people use an accumulator to do the job but here I'm not returning directly the result of the recursive call which makes this idea hard to implement.

EDIT

the first recursive call should have been cache[n] = 1 + countChain(n / 2, cache) instead of cache[n] = 1 + countChain(n, cache)

EDIT 2

Someone asked for example data so I will simply type in the whole code (not that long) for a better understanding.

import time
import sys
import math

def main():
    target = int(sys.argv[1])

    start = time.time()
    longest = 0
    answer = -1

    for i in range(int(target/2), target):
        if countChain(i) > longest:
            longest = countChain(i)
            answer = i

    print("Result = ", answer, " in ", (time.time() - start), " seconds")

def countChain(n,cache={1:0}):
    if n not in cache: 
        if n % 2 == 0:
            cache[n] = 1 + countChain(n//2, cache)
        else:
            cache[n] = 2 + countChain((3 * n + 1) // 2, cache)
    return cache[n]

if __name__ == "__main__":
    main()

The usual input is 1000000

Also the else should have been 2 + ...

7
  • 2
    cache[n] = 1 + countChain(n, cache) are you sure about this? you are invoking the method with the same params again. This seems to me like and endless recursion Commented Feb 20, 2019 at 17:02
  • 1
    Your fouction is not valid : cache[n] = 1 + countChain(n, cache) always loops. correct it first. Commented Feb 20, 2019 at 17:02
  • Blind mistake, sorry about that Commented Feb 20, 2019 at 17:06
  • 1
    Also, once you've fixed the endless recursion, you can increase the recursion limit in python using sys.setrecursionlimit() Commented Feb 20, 2019 at 17:14
  • 1
    I think the good count is 2 + countChain((3 * n + 1) / 2, cache) , and not 3+.... Commented Feb 20, 2019 at 17:54

2 Answers 2

3

You can always convert a recursive function to an iterative one by using a stack. Here's how I would do it:

def countChain(n):
    cache = { 1: 0 }
    stack = [n]
    while n not in cache:
      n_curr = stack.pop()

      if n_curr % 2 == 0:
        if n_curr / 2 in cache:
          cache[n_curr] = 1 + cache[n_curr / 2]
        else:
          stack.append(n_curr)
          stack.append(n_curr / 2)
      else:
        if (3 * n_curr + 1) / 2 in cache:
          cache[n_curr] = 3 + cache[(3 * n_curr + 1) / 2]
        else:
          stack.append(n_curr)
          stack.append((3 * n_curr + 1) / 2)

    return cache[n]
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks ! This is what I was looking for. Slower than recursion but I see how it should work now, thanks a lot !
No problem! The solution I gave is definitely naive and could be optimized but I wanted to demonstrate the process of turning a recursive approach to an iterative one in a standardized way.
2

Three versions: The natural recursive and cached one :

def countchain_recursive_cache(n):
    if n not in cache: 
        if n % 2 == 0:
            cache[n] = 1 + countchain_recursif(n//2)
        else:
            cache[n] = 1 + countchain_recursif(3 * n + 1)
    return cache[n]

A pure iterative one:

def countchain_iterative(n):
    count=0
    while n>1:
        if n%2 == 0 : 
            n //= 2
            count += 1
        else :
            n = (3*n+1)//2
            count += 2
    return count

And a stackless cached version :

def countchain_iterative_cache(n):
    count = 0
    n0=n
    while n not in cache:
            count += 1
            if n%2 == 0 : n //= 2
            else : n = 3*n+1
    count+= cache[n]
    n=n0
    while n not in cache:
            cache[n]=count
            count-=1
            if n%2 == 0 : n //= 2
            else : n = 3*n+1
    return cache[n]

The cache mechanism have no interest if the cache is in the function. it must be global to accelerate further calls.

Here are the timings :

%time  for i in range(1,10**4) : countchain_iterative(i)
cache={1:0}
%time  for i in range(1,10**4) : countchain_iterative_cache(i)
cache={1:0}
%time  for i in range(1,10**4) : countchain_recursive_cache(i)
%time  for i in range(1,10**4) : countChain_morcedist(i)

# respectively :
Wall time: 490 ms
Wall time: 80 ms
Wall time: 54 ms
Wall time: 3.82 s

But the most efficient way if the uncached iterative way if you just want one count chain :

%time countchain_iterative(10**10_000)  
Wall time: 6.37 s
Out[381]: 177856

And finally, I conjecture you will never crash the fast recursive function in a iterative construction :

%time for i in range(1,10**7): countchain_recursif(i)
Wall time: 1min 8s

2 Comments

This looks fine, but the OP asked for an iterative conversion?
This fixes one of the many issues but yes, the main idea was to turn this function into an iterative function

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.