0

The 3n+1 challenge is quite popular and can be found here

I've created a solution in python below and also here on github

def solution(i, j):
    count = toRtn = 1
    for n in xrange(i, j+1):
        count = 1
        while n != 1:
            if n % 2 == 0:
                n = n/2
            else:
                n = (3 * n) + 1
            count += 1
        toRtn = max(toRtn, count)
    return toRtn

print solution(100, 200)
print solution(201, 210)

I have several questions:

  1. Can and should this be re-written as a recursion? Whould that improve efficiency?

  2. How can I calculate the complexity of this function?

  3. Is there an online judge for Python for those challenges?

Sam Hammamy
  • 10,819
  • 10
  • 56
  • 94
  • 2
    Recursion is (almost?) never the most efficient solution, if straightforward iterative approaches exist. Also, it's frowned upon to ask three questions in one question. – keyser Dec 07 '14 at 20:23
  • This 'challenge' is known as the Collatz conjecture. – flumpb Dec 07 '14 at 20:25
  • I accept the -1 for asking three question in one, but why the other -1? – Sam Hammamy Dec 07 '14 at 20:26
  • @SamHammamy Yeah Im looking for that answer actually. I got -4 for I just wrote Python as python. Nice users ha :-) – GLHF Dec 07 '14 at 20:27
  • Recursion is tricky with Python because there is always a limit on how many times a function is allowed to call itself without returning. By default, this limit is 1000. So, if you enter a number that will produce a chain longer than 1000, you'll get an error. – Alex Riley Dec 07 '14 at 20:56
  • @ajcr - you can alter the maximum recursion depth quite easily. – will Dec 07 '14 at 21:09
  • @will how can that be done? – Sam Hammamy Dec 07 '14 at 21:11
  • @will: yes, but the point is that you cannot set it infinitely high. Sooner or later, however high it's set, you might input a number that will exceed it. There's some discussion [here](http://stackoverflow.com/questions/3323001/maximum-recursion-depth) on why it's not sensible to set it too high. – Alex Riley Dec 07 '14 at 21:18
  • No you can't. You can mimic [unlimited recursion using stackless python](http://pypy.readthedocs.org/en/latest/stackless.html). You can also considerably reduce the amount of recursision needed by using a cache / momoizing (i'm still not a fan of that ridiculous word), as mentioned in the accepted answer. – will Dec 08 '14 at 12:53
  • @ajcr But yes, i do agree with the sentiment, it's essentially using the language for something it wasn't designed for. – will Dec 08 '14 at 12:55

3 Answers3

3

You can define a recursive method to calculate 3n+1

def threen(n):
    if n ==1:
        return 1
    if n%2 == 0:
        n = n/2
    else:
        n = 3*n+1
    return threen(n)+1

To avoid calculating same numbers twice you can cache values

cache = {}
def threen(n):
    if n in cache:
        return cache[n]
    if n ==1:
        return 1
    orig = n
    if n%2 == 0:
        n = n/2
    else:
        n = 3*n+1
    count = threen(n)+1
    cache[orig] = count
    return count

Now you can use this in a loop

def max_threen(i, j):
    max3n = 0
    for n in xrange(i, j+1):
        max3n= max(threen(n), max3n)
    print i, j, max3n

max_threen(100,201)

Now you can compare this with your version :), it may consume a lot of memory but could be faster for certain ranges, obviously a non recursive method would be faster if you are caching values, but recursion is fun and more readable, but in anycase iterative version will be faster with caching (not tested)

cache = {}
def solution(i, j):
    count = toRtn = 1
    for n in xrange(i, j+1):
        count = 1
        orig = n
        if n in cache:
            count = cache[n]
        else:
            while n != 1:
                if n % 2 == 0:
                    n = n/2
                else:
                    n = (3 * n) + 1
                count += 1

            cache[orig] = count
        toRtn = max(toRtn, count)
    return toRtn
Anurag Uniyal
  • 85,954
  • 40
  • 175
  • 219
0
  1. No and no; recursion is rarely (if ever) used for better performance.
  2. You can't, since it hasn't even been established that it will return for all values of n. (Nobody has found one that it doesn't return for, but no one has proved that there isn't one, either.)
  3. Not to my knowledge.
Scott Hunter
  • 48,888
  • 12
  • 60
  • 101
  • Disagreed on 1. With this problem, recursion is an opportunity to use memoization, which can provide a significant speedup when you avoid redoing work. So it is possible for recursion to improve efficiency, particularly if you're doing a wide range. That said, rewriting a recursive version to an iterative form will probably be a speedup. – btilly Dec 07 '14 at 20:30
  • 1
    And memoization can't be done w/o recursion because...? – Scott Hunter Dec 07 '14 at 20:38
  • @ScottHunter, The usual meaning of memoization is based around function calls. Since there is only one function call without recursion, memoization won't help. You can (and should) use [dynamic programming](http://en.wikipedia.org/wiki/Dynamic_programming) though – John La Rooy Dec 07 '14 at 21:06
  • A simple cache would do the job nicely. – Scott Hunter Dec 07 '14 at 21:15
  • @ScottHunter I find it easier to initially implement memoization with recursion. But I agree that rewriting that version iteratively would be likely to improve performance. – btilly Dec 08 '14 at 16:19
0

1) Strictly speaking recursion itself can't optimize itself. Recursion is not about optimization, but about clarity when needed. However you could use recursion with memoization to boost the performance: http://en.wikipedia.org/wiki/Memoization

2) Complexity of the function sum of the complexity of each of its statements. So to calculate the whole complexity you should construct a table of two columns for each line: [Number of times called], [Complexity] and sum up each of the row. Let's look at the following code:

 def print_N_times(N):
    print "------"        #Line1
    for i in range(0,N):  #Line2
        print "easy"      #Line3
    print "------"        #Line4 
-------------------------------------------------------
| Line number | Number of times executed | Complexity | 
|  Line 1     |        1                 |     1      |
|  Line 2     |        N+1               |     1      |
|  Line 3     |        N                 |     1      |
|  Line 4     |        1                 |     1      |
-------------------------------------------------------

(Here 1 is as a replacement for constant complexity)

So by multiplying each column and summing them up:

1*1 + (N+1)*1 + N*1 + 1*1 = 2*N + 3 = O(N), so this function has linear complexity.

3) There're a lot of judges, but spoj.pl accepts variety of programming languages:

http://www.spoj.com/problems/PROBTRES/

Edit: Correctly noted in comments that for 3n+1 problem you don't know each value of the [Number of times of executed] column, or whether the function will be finished ever. For more info see:

https://en.wikipedia.org/wiki/Collatz_conjecture

manzur
  • 682
  • 3
  • 7
  • 2
    But if it can't be proven that it will halt how can it have O(N) complexity – Anurag Uniyal Dec 07 '14 at 20:54
  • @AnuragUniyal Really? It seems you're blindly replacing concrete with general. It the same as you would say "That's an NP-hard problem" by looking at 2SAT. – manzur Dec 07 '14 at 21:05
  • @manzur: Yes, really. You can't establish how many times the loop will execute, because sometimes n *increases* (for example, n=3). In fact, its a pretty famous open problem whether or not the function is defined for all n. – Scott Hunter Dec 07 '14 at 21:21
  • @ScottHunter sure, but my evaluation is for the function I wrote, not for 3n+1. – manzur Dec 07 '14 at 21:36
  • 3
    @manzur: But your technique does not apply to this problem, and thus is not appropriate in an answer to it. – Scott Hunter Dec 07 '14 at 21:39
  • also recursion can only add readability, iterative version can be cached for better performance – Anurag Uniyal Dec 08 '14 at 04:40
  • @AnuragUniyal a smart compiler will turn tail recursion into iteration automatically. No need to worry. – John Dvorak Dec 08 '14 at 04:48