3

I have the following code for calculating the factorial of a number in python. but I couldnt understand why I am getting the answer as 1. can some one correct my code. I want to calculate the factorial without using recursion.

def factorial (n):
        result =1 
        num = n
        while n<1:
            result = result * num
            num = num -1
        return result

    factorial(5)
    1
dangerous
  • 171
  • 1
  • 1
  • 11
  • The loop condition is depends on `n`, but you change only `result` and `num` in the loop. The loop will run 0 or infinite times. – LeeNeverGup Feb 12 '15 at 10:58
  • As [Frerich](http://stackoverflow.com/a/28476327/1075247) and [PM 2Ring](http://stackoverflow.com/a/28477818/1075247) state at length, `math.factorial` is much faster. – AncientSwordRage Feb 12 '15 at 13:38
  • @Pureferret I think we have to assume here, that the OP's goal is not just that he has some numbers and wants to know what their factorial is. – jwg Jan 20 '16 at 15:46

9 Answers9

9
while n < 1:

should instead be

while num > 1:
Simon Gibbons
  • 6,969
  • 1
  • 21
  • 34
6

Others pointed out what's wrong with your code, but I wanted to point out that a factorial function really lends itself to a more functional (as in: functional programming) solution; this avoids the issue with getting the condition for the while loop altogether because you don't have any loop at all. The insight is that the factorial of n is the product of 1..n, and the product can be defind very easily using Python's reduce function. To avoid losing performance out of sight, here's what my Python 2.7 interpreter gives for your (fixed) code:

python -m timeit -s "import original" "original.factorial(10)"
1000000 loops, best of 3: 1.22 usec per loop

A shorter version (a one-liner) which is more declarative is possible:

def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))

...alas, it's slower:

python -m timeit -s "import func1" "func1.factorial(10)"
1000000 loops, best of 3: 1.98 usec per loop

However, this can be solved by using xrange instead of range and operator.mul instead of a custom lambda:

import operator

def factorial(n):
    return reduce(operator.mul, xrange(1, n+1))

And for me, this is even faster than the original code:

python -m timeit -s "import func2" "func2.factorial(10)"
1000000 loops, best of 3: 1.14 usec per loop

Personally, I'd factor out the reduce call to make the code even clearer (at the expense of a tiny little bit of performance):

import operator

def product(it):
    return reduce(operator.mul, it)

def factorial(n):
    return product(xrange(1, n+1))

I like this version for being fast, and for being explicit: the factorial is defined to be the product of the range [1..n+1[ (i.e. n+1 is excluded). The performance difference becomes more apparent if you try to compute the factorial of larger numbers:

python -m timeit -s "import original" "original.factorial(30)"
100000 loops, best of 3: 5.25 usec per loop

vs.

python -m timeit -s "import func3" "func3.factorial(30)"
100000 loops, best of 3: 3.96 usec per loop
Frerich Raabe
  • 90,689
  • 19
  • 115
  • 207
  • How does this answer the question? – AncientSwordRage Feb 12 '15 at 13:04
  • 1
    @Pureferret: It doesn't answer the question (the question was already answered), it extends the existing answers by showing alternative implementations which would have avoided the problem altogether (because no explicit `for` loop is needed). – Frerich Raabe Feb 12 '15 at 13:24
  • If it doesn't answer the question, why did you answer? Why not just comment? – AncientSwordRage Feb 12 '15 at 13:26
  • 1
    @Pureferret A comment doesn't provide enough space (let alone readability) for what I wrote. – Frerich Raabe Feb 12 '15 at 13:34
  • How about 'By the way, this isn't the best approach ' ? – AncientSwordRage Feb 12 '15 at 13:35
  • 3
    @Pureferret [Links to external content break](http://meta.stackexchange.com/questions/80621/links-to-external-content-how-do-we-mitigate-degradation-of-so-as-external-link) and (as that particular answer quotes) "one of the main goals of SO is to provide a comprehensive Q & A repository (and not just a question and answer forum)". I'd be happy if the author of the accepted answer would extend his text with what I wrote, but as long as that's not the case, adding another answer which takes a step back is a fair compromise. If it makes you happy, I can also add a "Change `n<1` to `num>1`." ;-) – Frerich Raabe Feb 12 '15 at 13:37
  • That just discusses links *instead of* answers. What you want is covered by #1: `Allow external links only for backup to answers` as in your answer, backs up the accepted answer. – AncientSwordRage Feb 12 '15 at 13:40
  • @FrerichRaabe, I "enjoy" it when people with nothing useful to say start haggling over protocol – volcano Feb 12 '15 at 13:55
  • 1
    @FrerichRaabe - Great explanation. I couldnt change the accepted answer, as this doesnt answer my question. But your explanation introduced me to lots of new concepts. I have upvoted your solution. – dangerous Feb 13 '15 at 10:17
  • @dangerous: Simon's answer is short and to the point, so it's perfectly fine that it's the accepted answer (I even upvoted it myself). True, some of the answers here don't address your primary question, but they are attempts to enhance the value of the existing answers. FWIW, I've enhanced the answer I submitted yesterday. :) – PM 2Ring Feb 13 '15 at 11:19
4

while 5 < 1 is always false, so result = 1 is returned. That's wrong.

  • Should I learn recursion. I am really confused with the concept of recursion. or can I get away with loops in python. – dangerous Feb 12 '15 at 11:06
  • You should of course learn the concept of recursion. Which approach is best depends on the concrete situation. –  Feb 12 '15 at 11:10
4

Let's see:

  1. You set n=5 when you call the function.
  2. You tell Python that while n < 1 do things.
  3. n is already bigger than 1, it won't execute the while code .
  4. Your code returns result, set to 1 in the first line of the definition.
Luis González
  • 3,199
  • 26
  • 43
3

As Simon Gibbons explained, your code has

while n < 1:

instead of

while num > 1:

So you have less than instead of greater than, thus the test in your while statement will fail immediately. However, if you changed it to while n > 1: it would loop forever, since you never change the value of n inside the while loop.

Haresh Shyara posted a corrected version of your code, reproduced here:

def factorial(n):
    result = 1
    while n > 1:
        result = result * n
        n = n - 1
    return result

Note that this code doesn't bother with copying n to num - it just uses n directly. This will not affect the argument that you call the function with because

  1. Python integers are immutable and
  2. n = n - 1 actually creates a new local object named n.

I was inspired by Frerich Raabe's answer to write a program to do the timings of the various solutions offered here in a more systematic fashion. I've also included the math.factorial() and a simple for loop based function I just threw together.

I've optimized the functions that call operator.mul slightly by defining mul = operator.mul, but I had to supply an initial parameter of 1 to those functions that use reduce() so that they don't fail on factorial(0) (which should return 1).

I've approximately ordered the functions from fastest to slowest.

I've just enhanced this program to make it a little easier to run multiple tests and to add new functions to test. Also, it now prints a brief description of each function, using the function's docstring. And before running the timing tests it verifies that each function calculates correct values.

#!/usr/bin/env python

''' Test and time various implementations of the factorial function

    From https://stackoverflow.com/q/28475637/4014959

    Written by PM 2Ring 2015.02.13
'''

import operator
import math
from timeit import Timer

factorial0 = math.factorial

def factorial0a(n):
    ''' call math.factorial '''
    return math.factorial(n)

def factorial1(n):
    ''' for loop'''
    p = 1
    for i in xrange(2, n+1):
        p *= i
    return p

mul = operator.mul

def product(it):
    return reduce(mul, it, 1)

def factorial2(n):
    ''' reduce with op.mul '''
    return reduce(mul, xrange(1, n+1), 1)

def factorial3(n):
    ''' call product() '''
    return product(xrange(1, n+1))    

def factorial4(n):
    ''' while loop '''
    result = 1
    while n > 1:
        result = result * n
        n = n - 1
    return result

def factorial4a(n):
    ''' while loop with assignment operators '''
    result = 1
    while n > 1:
        result *= n
        n -= 1
    return result

def factorial5(n):
    ''' recursive '''
    if n <= 1:
        return 1;
    else:
        return n*factorial5(n-1)

def factorial6(n):
    ''' reduce with lambda '''
    return reduce(lambda res, val: res*val, xrange(n, 0, -1), 1)

funcs = (
    factorial0,
    factorial0a,
    factorial1,
    factorial2,
    factorial3,
    factorial4,
    factorial4a,
    factorial5,
    factorial6,
)

def verify(n):
    ''' Check that each function calculates the same result as math.factorial '''
    r = xrange(n)
    fac = [factorial0(i) for i in r]
    rc = True
    for func in funcs[1:]:
        for i in r:
            v = func(i)
            if v != fac[i]:
                print 'Error: %s(%d) returns %d instead of %d' % (func.func_name, i, v, fac[i])
                rc = False
    return rc

def time_test(arg=10, loops=100000, reps=3):
    ''' Print timing stats for all the factorial functions '''
    print 'Arg = %d, Loops = %d, Repetitions = %d' % (arg, loops, reps)

    for func in funcs:
        #Get function name and docstring
        try:
            fname = func.func_name
            fdoc = func.__doc__
        except AttributeError:
            #Math.factorial has no name, and we can't modify its docstring
            fname = 'factorial0'
            fdoc = ' math.factorial itself '

        print '\n%s:%s' % (fname, fdoc)
        t = Timer('%s(%d)' % (fname, arg), 'from __main__ import %s' % fname)
        r = t.repeat(reps, loops)
        r.sort()
        print r
    print '\n'

def main():
    if not verify(100): exit(1)
    time_test(arg=5, loops=500000, reps=4)
    time_test(arg=10, loops=200000, reps=4)
    time_test(arg=50, loops=100000, reps=4)

if __name__ == '__main__':
    main()

output

Arg = 5, Loops = 500000, Repetitions = 4

factorial0: math.factorial itself 
[0.30838108062744141, 0.3119349479675293, 0.31210899353027344, 0.32166290283203125]

factorial0a: call math.factorial 
[0.62141299247741699, 0.62747406959533691, 0.63309717178344727, 0.66500306129455566]

factorial1: for loop
[1.4656128883361816, 1.476855993270874, 1.4897668361663818, 1.5052030086517334]

factorial2: reduce with op.mul 
[1.5841941833496094, 1.5868480205535889, 1.6007061004638672, 1.6253509521484375]

factorial3: call product() 
[1.8745129108428955, 1.8750350475311279, 1.8822829723358154, 1.9097139835357666]

factorial4: while loop 
[1.1264691352844238, 1.1348199844360352, 1.1348659992218018, 1.178135871887207]

factorial4a: while loop with assignment operators 
[1.1867551803588867, 1.1881229877471924, 1.1893219947814941, 1.2020411491394043]

factorial5: recursive 
[1.9756920337677002, 1.9862890243530273, 1.9910380840301514, 2.0284240245819092]

factorial6: reduce with lambda 
[2.8342490196228027, 2.8369259834289551, 2.8390510082244873, 2.8969988822937012]


Arg = 10, Loops = 200000, Repetitions = 4

factorial0: math.factorial itself 
[0.24756813049316406, 0.24919605255126953, 0.26395106315612793, 0.28582406044006348]

factorial0a: call math.factorial 
[0.3732609748840332, 0.37482404708862305, 0.37592387199401855, 0.38288402557373047]

factorial1: for loop
[0.88677501678466797, 0.89632201194763184, 0.89948821067810059, 0.90272784233093262]

factorial2: reduce with op.mul 
[0.89040708541870117, 0.89259791374206543, 0.89863204956054688, 0.90652203559875488]

factorial3: call product() 
[1.0093960762023926, 1.031667947769165, 1.2325050830841064, 1.7492170333862305]

factorial4: while loop 
[0.93423891067504883, 0.93978404998779297, 0.94000387191772461, 0.95153117179870605]

factorial4a: while loop with assignment operators 
[0.97296595573425293, 0.97462797164916992, 0.98288702964782715, 1.0095341205596924]

factorial5: recursive 
[1.6726200580596924, 1.6786048412322998, 1.691572904586792, 1.6946439743041992]

factorial6: reduce with lambda 
[1.8484599590301514, 1.8502249717712402, 1.8615908622741699, 1.9228360652923584]


Arg = 50, Loops = 100000, Repetitions = 4

factorial0: math.factorial itself 
[1.6450450420379639, 1.6641650199890137, 1.6790158748626709, 1.7192811965942383]

factorial0a: call math.factorial 
[1.7563199996948242, 2.0039281845092773, 2.1530590057373047, 2.3621060848236084]

factorial1: for loop
[2.7895750999450684, 2.8117640018463135, 2.8381040096282959, 3.0019519329071045]

factorial2: reduce with op.mul 
[2.4697721004486084, 2.4750289916992188, 2.4813871383666992, 2.5051541328430176]

factorial3: call product() 
[2.4983038902282715, 2.4994339942932129, 2.5271379947662354, 2.5356400012969971]

factorial4: while loop 
[3.6446011066436768, 3.650169849395752, 3.6579680442810059, 3.7304909229278564]

factorial4a: while loop with assignment operators 
[3.7421870231628418, 3.7477319240570068, 3.7655398845672607, 3.7749569416046143]

factorial5: recursive 
[5.523845911026001, 5.5555410385131836, 5.5760359764099121, 6.2132260799407959]

factorial6: reduce with lambda 
[4.9984982013702393, 5.0106558799743652, 5.0363597869873047, 5.0808289051055908]

As with all timeit results, the fastest entries in each list are the significant ones, the slower ones should be ignored.

From the timeit docs (courtesy of Ffisegydd):

... the lowest value gives a lower bound for how fast your machine can run the given code snippet; higher values in the result vector are typically not caused by variability in Python’s speed, but by other processes interfering with your timing accuracy. So the min() of the result is probably the only number you should be interested in...

Community
  • 1
  • 1
PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
  • Yes even your solution looks great. but I couldnt understand this part def factorial(n): return reduce(lambda res, val: res*val, xrange(n, 0, -1), 1). could you explain whats going in this factorial code? – dangerous Feb 13 '15 at 12:10
  • @Dangerous: volcano's code is very similar to the other versions that use `reduce`. Do you understand what [reduce()](https://docs.python.org/2/library/functions.html#reduce) does? Do you understand what [lambda](http://stackoverflow.com/a/1583630/4014959) does? – PM 2Ring Feb 13 '15 at 12:31
  • @dangerous: Ok, so what bit don't you understand? That `xrange()` is a bit silly, but it just iterates over the integers from n down to 1. – PM 2Ring Feb 13 '15 at 13:05
  • I am confused with the exact application of reduce in this context. reduce takes a function and applies it to each iterable. So here lambda function multiplies two numbers. so if I want to calculate factorial of 5 how is that implemented using this code? – dangerous Feb 13 '15 at 13:12
  • @dangerous: For each element in the iterable `reduce` passes the function two values, one is the current result (res), the other is the current element from the iterable (val), the result of the function is saved by `reduce` so it can pass it back to the function on the next step. The example code in the docs for `reduce` that I linked earlier illustrates that process. I'll post some code that uses a `for` loop which is equivalent to volcano's code, but I'll edit it into volcano's question, since it is his code I'm explaining. – PM 2Ring Feb 13 '15 at 13:36
  • @dangerous: My original `for` loop solution is already in my answer: it's `factorial1()`. I won't post it as a separate answer because one person posting multiple answers to a question is **highly** discouraged on all Stack Exchange sites. But please feel free to up-vote this answer. :) But if you're talking about the `for` loop which is equivalent to volcano's code, please see his answer. – PM 2Ring Feb 13 '15 at 13:59
  • I don't think speed optimization is the important goal here. – jwg Jan 20 '16 at 15:48
  • @jwg: Sure, the primary answer to the OP's question is the explanation of what's wrong with their logic. However, Stack Overflow isn't merely about answering the OP's specific question, SO answers should be useful to future readers as well. Writing a factorial function is a common beginners assignment and it's nice to have a variety of algorithms in one place so people can compare & contrast them. – PM 2Ring Jan 21 '16 at 06:07
  • (cont) If someone wants speed they shouldn't be using their own factorial function: they should use the built-in `math.factorial`, or perhaps the 3rd party `gmpy` or `mpmath` modules. And if they _really_ need speed they shouldn't by using standard Python, they should be using something that can be compiled to machine code. – PM 2Ring Jan 21 '16 at 06:07
1

Your code will not enter in the while loop itself. Change it from n<1 to n>1. If for example, finding factorial of 5, n<1 will be treated as 5<1 which is false.

kvivek
  • 3,321
  • 1
  • 15
  • 17
1
def factorial(n):
    result = 1
    while n > 1:
        result = result * n
        n = n - 1
    return result

print factorial(5)
120
Haresh Shyara
  • 1,826
  • 10
  • 13
  • 1
    This type of answer is not very helpful. You are just providing a fixed code snippet. Please explain what is wrong with the original snippet and why your version works. Even though the [accepted answer](http://stackoverflow.com/a/28475657/354577) only has a small bit of code, it is better because it explains the problem in a much clearer way. – ChrisGPT was on strike Feb 12 '15 at 13:00
  • (while n < 1) this is wrong in origional snippet n=5 so ( 5<1 ) it if false than your control is not execute the body part return the result=1. and second is you use variable n in while loop and decrease num variable – Haresh Shyara Feb 12 '15 at 13:11
0

Try this, it's cleaner:

def factorial( n ):
    if n <= 1:
        return 1;
    else:
        return n*factorial(n-1)

This is a recursive way to solve the problem.

Luis González
  • 3,199
  • 26
  • 43
  • 2
    Why is the recursive way cleaner? –  Feb 12 '15 at 11:03
  • I dont understand recursion properly. thats why I am using loops ;) – dangerous Feb 12 '15 at 11:03
  • Maybe the word isn't 'cleaner' but in my opinion it's better. You use less code lines, and you use the common or natural way to understand the factorial operation: N! = N*(N-1)! , unless N = 1 and then N! = 1. It's really a personal opinion, – Luis González Feb 12 '15 at 11:08
  • 5
    Recursion is not optimized in Python, and it has a limit on the recursion depth (although the limit can be modified). Some problems are best approached using recursion (eg tree processing), but in general it's best to avoid recursion if you can easily solve your problem with an iterative algorithm. – PM 2Ring Feb 12 '15 at 11:30
  • 1
    Second @PM2Ring - even if stack was infinite, there is still a penalty for heavy stack usage - and there's no real reason to use it for problems that may be easily solved by iteration – volcano Feb 12 '15 at 11:36
  • @volcano: Indeed; heavy stack usage not only adds to the memory usage of a program, it also tends to slow it down. And as the timings I just posted show, the recursive factorial is relatively slow. – PM 2Ring Feb 12 '15 at 12:48
  • For what it's worth, it would have been possible to adjust the definition such that the function is tail-recursive but alas [Python does not optimize tail recursion](http://stackoverflow.com/questions/13591970/does-python-optimize-tail-recursion) so even though a recursive definition nicely follows the mathematical definition, it's not viable. – Frerich Raabe Feb 13 '15 at 10:39
-1

Speaking of short Pythonic code

def factorial(n):
    return reduce(lambda res, val: res*val, xrange(n, 0, -1), 1)

For those that don't understand how volcano's code works, here is a close approximation:

''' Equivalent code by PM 2Ring '''

def f(res, val):
    return res * val

def factorial(n):
    res = 1
    for val in xrange(n, 0, -1):
        res = f(res, val)
    return res
PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
volcano
  • 3,578
  • 21
  • 28
  • This doesn't look anything like questioners code, so how does this help? – AncientSwordRage Feb 12 '15 at 13:05
  • @Pureferret, stupid reason to downvote - but be my guest. Just out of curiosity - did you downvote all answers in this thread that provided alternative solutions? – volcano Feb 12 '15 at 13:46
  • @volcano- your solution looks some what different. can you explain how the function reduce(lambda res, val: res*val, xrange(n, 0, -1), 1) works? I know that lambda res,val : res*val multiplies two numbers. But i couldnt get the logic behind reduce(function, xrange(n,0,-1),1) . could you elaborate your answer. – dangerous Feb 13 '15 at 10:25
  • Actually, backward range is meaningless - was not thinking straight when I wrote it. Initial value is redundant too - I do not use reduce often, so I missed that initially – volcano Feb 13 '15 at 14:18
  • 1
    The initial value _isn't _ redundant: consider what happens with `factorial(0)`. – PM 2Ring Feb 14 '15 at 07:57