# L-21.0 MCS 260 Mon 4 Aug 2014 : factorial.py
"""
Our first example of a recursive function computes the factorial.
We provide several definitions of the factorial below.
"""
def factorial(k):
    """
    computes the factorial of k recursively
    """
    if k <= 1:
        return 1
    else:
        return k*factorial(k-1)

def factexcept(k):
    """
    when the recursion depth is exceeded
    the factorial of k is computed iteratively
    """
    if k <= 1:
        return 1
    else:
        try:
            return k*factexcept(k-1)
        except RuntimeError:
            print 'run time error raised'
            result = 1
            for i in range(2, k+1):
                result = result*i
            return result

def factotrace(nbr, k):
    """
    prints out trace information in call k
    the initial value for k should be zero
    """
    prt = k*' '
    prt = prt + 'factotrace(%d,%d):' % (nbr, k)
    if nbr <= 1:
        print prt + ' base case, return 1'
        return 1
    else:
        print prt + ' call for n-1 = ' + str(nbr-1)
        result = nbr*factotrace(nbr-1, k+1)
        print prt + ' returning %d' % result
        return result

def factaccu(nbr, acc):
    """
    accumulates the factorial in acc
    call factaccu initially with acc = 1
    """
    if nbr <= 1:
        return acc
    else:
        return factaccu(nbr-1, acc*nbr)

def factatrace(nbr, acc, k):
    """
    accumulates the factorial in acc,
    k is used to trace the calls
    initialize acc to 1 and k to 0
    """
    prt = k*' '
    prt = prt + 'factatrace(%d,%d,%d)' % (nbr, acc, k)
    if nbr <= 1:
        print prt + ' returning ' + str(acc)
        return acc
    else:
        print prt + ' call for n-1 = ' + str(nbr-1)
        result = factatrace(nbr-1, acc*nbr, k+1)
        print prt + ' returning %d' % result
        return result

def main():
    """
    prompts the user for a number
    and returns the factorial of it
    """
    nraw = raw_input('give a natural number n : ')
    nbr = int(nraw)
    if nbr < 998:
        fac = factorial(nbr)
        print 'n! = ', fac
        fac = factaccu(nbr, 1)
        print 'n! = ', fac
    else:
        fac = factexcept(nbr)
        print 'n! = ', fac
    print 'len(n!) = ', len(str(fac))
    if nbr < 21:
        print 'tracing the basic factorial :'
        fac = factotrace(nbr, 0)
        print 'tracing factorial with accumulator :'
        fac = factatrace(nbr, 1, 0)

if __name__ == "__main__":
    main()
