tags:

views:

315

answers:

6

Say you have a value like this:

n = 5

and a method that returns the factorial of it, like so:

Factorial ( 5 )

How do you handle multiple values:

nums = [1,2,3,4,5]

Factorial ( nums )

so it returns the factorials of all these values as a list.

What's the cleanest way to handle this, without writing 2 methods? Does python have a good way to handle these kind of situations?

+9  A: 

List comprehension:

[fac(n) for n in nums]

EDIT:

Sorry, I misunderstood, you want a method that handles both sequences and single values? I can't imagine why you wouldn't do this with two methods.

def factorial(n):
    # implement factorial here
    return answer

def factorial_list(nums):
    return [factorial(n) for n in nums]

The alternative would be to do some sort of type-checking, which is better avoided unless you have some terribly compelling reason to do so.

EDIT 2:

MizardX's answer is better, vote for that one. Cheers.

bouvard
Actually, I like your answer more. I think writing this as one function makes for much messier code.
daf
Well, so do I. :P But MizardX actually answered his question, which I didn't do, so its only fair he gets the votes. :)
bouvard
Thanks, how does python find the correct function when you used a collection?
Joan Venge
-Python- doesn't find anything. Its up to you to ensure that you are calling the correct function. What situation are you imagining in which you wouldn't know in advance if you were dealing with individual or structured values? I think that is likely a situation you want to avoid.
bouvard
If you really _want_ to do this, then I would suggest converting your individual value to a list and always using the list form. That is: factorial_list([7]) works just as well as factorial_list([1,2,3]), you just now that your output will always be a list (which is a good thing).
bouvard
I don't believe that having an function whose output format varies in relation to its input format would ever be a good design decision. Its better to have explicit functions for each case.
bouvard
+1 for being more Pythonic. Sometimes the best answer to "How do I do X if my goal is Y" is "You shouldn't be doing X at all."
Miles
+6  A: 

If you're asking if Python can do method overloading: no. Hence, doing multi-methods like that is a rather un-Pythonic way of defining a method. Also, naming convention usually upper-cases class names, and lower-cases functions/methods.

If you want to go ahead anyway, simplest way would be to just make a branch:

def Factorial(arg):
  if getattr(arg, '__iter__', False): # checks if arg is iterable
    return [Factorial(x) for x in arg]
  else:
    # ...

Or, if you're feeling fancy, you could make a decorator that does this to any function:

def autoMap(f):
    def mapped(arg):
        if getattr(arg, '__iter__', False):
            return [mapped(x) for x in arg]
        else:
            return f(arg)
    return mapped

@autoMap
def fact(x):
    if x == 1 or x == 0:
        return 1
    else:
        return fact(x-1) + fact(x-2)

>>> fact(3)
3
>>> fact(4)
5
>>> fact(5)
8
>>> fact(6)
13
>>> fact([3,4,5,6])
[3, 5, 8, 13]

Although a more Pythonic way is to use variable argument lengths:

def autoMap2(f):
    def mapped(*arg):
        if len(arg) != 1:
            return [f(x) for x in arg]
        else:
            return f(arg[0])
    return mapped

@autoMap2
def fact(n):
# ...

>>> fact(3,4,5,6)
[3, 5, 8, 13]

Putting the two together into a deep mapping decorator:

def autoDeepMap(f):
    def mapped(*args):
        if len(args) != 1:
            return [mapped(x) for x in args]
        elif getattr(args[0], '__iter__', False):
            return [mapped(x) for x in args[0]]
        else:
            return f(args[0])
    return mapped

@autoDeepMap
def fact(n):
# ...

>>> fact(0)
1
>>> fact(0,1,2,3,4,5,6)
[1, 1, 2, 3, 5, 8, 13]
>>> fact([0,1,2,3,4,5,6])
[1, 1, 2, 3, 5, 8, 13]
>>> fact([0,1,2],[3,4,5,6])
[[1, 1, 2], [3, 5, 8, 13]]
>>> fact([0,1,2],[3,(4,5),6])
[[1, 1, 2], [3, [5, 8], 13]]
Andrey Fedorov
You could make the decorator recursive by changing [f(x) for x in arg] to [mapped(x) for x in arg].
MizardX
MizardX: you just blew my mind. Nice idea :)
Andrey Fedorov
But this is fibonacci... :)
Nikhil Chelliah
Nikhil: good call! :-P I'll leave it for the laugh, feel free to change it if you care to be encyclopedic.
Andrey Fedorov
+1: for decorators
J.F. Sebastian
Thanks, does variable argument lengths also handle the other 2 cases? Single and multiple values (through a list, etc)?
Joan Venge
Joan: single values - yes. List arguments - no, but it would certainly be interesting to add...
Andrey Fedorov
Added! Man, I <3 Python :)
Andrey Fedorov
+1 for "more Pythonic" autoMap2 and autoDeepMap. :-)
Ben Blank
+13  A: 
def Factorial(arg):
    try:
        it = iter(arg)
    except TypeError:
        pass
    else:
        return [Factorial(x) for x in it]
    return math.factorial(arg)

If it's iterable, apply recursivly. Otherwise, proceed normally.

Alternatively, you could move the last return into the except block.

If you are sure the body of Factorial will never raise TypeError, it could be simplified to:

def Factorial(arg):
    try:
        return [Factorial(x) for x in arg]
    except TypeError:
        return math.factorial(arg)
MizardX
getattr(obj, '__iter__', False) is a way to check if 'obj' is iterable without try/except (which might be slower).
Andrey Fedorov
@Andrey Fedorov: in most cases, the try/except is actually faster.
S.Lott
@MizardX: The body of `Factorial` is in front of you. Therefore you can be sure that `TypeError` *will* be raised e.g., when `arg` is not an iterable. The above two versions of `Factorial` do the same thing, but one is simpler.
J.F. Sebastian
@J.F. Sebastian: Factorial([1,2+0j]) would try to call math.factorial(2+0j), catch the TypeError, and try to call math.factorial([1,2+0j]). The first TypeError will be swallowed. You only want to catch the TypeError if it resulted from the argument not being iterable.
MizardX
+7  A: 

This is done sometimes.

def factorial( *args ):
    def fact( n ):
        if n == 0: return 1
        return n*fact(n-1)
    return [ fact(a) for a in args ]

It gives an almost magical function that works with simple values as well as sequences.

>>> factorial(5)
[120]
>>> factorial( 5, 6, 7 )
[120, 720, 5040]
>>> factorial( *[5, 6, 7] )
[120, 720, 5040]
S.Lott
Cleanest answer so far...
Nikhil Chelliah
Not good, as it returns a list even if the input is a simple number.
Georg
I'm completely unclear on what the requirements are for. The idea of a function that magically handles list and individual values in a "uniform" way is confusing. Or it's APL (or J or K).
S.Lott
S. Lott, does this also handle when you pass a collection directly, like list=[1,2,3,4,5] and then factorial (list)?
Joan Venge
In a way. I've updated the answer. Your use case is a very, very odd situation where you're trying to handle all these fundamentally different types in a single function. It doesn't really make a lot of sense.
S.Lott
+3  A: 

Or if you don't like the list comprehension syntax, and wish to skip having a new method:

def factorial(num):
    if num == 0:
        return 1
    elif num > 0:
        return num * factorial(num - 1)
    else:
        raise Exception("Negative num has no factorial.")

nums = [1, 2, 3, 4, 5]
# [1, 2, 3, 4, 5]

map(factorial, nums)
# [1, 2, 6, 24, 120, 720]
Nikhil Chelliah
+3  A: 

You might want to take a look at NumPy/SciPy's vectorize.

In the numpy world, given your single-int-arg Factorial function, you'd do things like

  vFactorial=np.vectorize(Factorial)
  vFactorial([1,2,3,4,5])
  vFactorial(6)

although note that the last case returns a single-element numpy array rather than a raw int.

timday