Using math.factorial in a lambda function with reduce()
I'm attempting to write a function that calculates the number of unique permutations of a string. For example aaa
would return 1
and abc
would return 6
.
(Pseudocode:)
len(string)! / (A!*B!*C!*...)
where A,B,C are the number of occurrences of each unique character. For example, the string 'aaa'
would be 3! / 3! = 1
, while 'abc'
would be 3! / (1! * 1! * 1!) = 6
.
My code so far is like this:
def permutations(n):
'''
returns the number of UNIQUE permutations of n
'''
from math import factorial
lst = []
n = str(n)
for l in set(n):
lst.append(n.count(l))
return factorial(len(n)) / reduce(lambda x,y: factorial(x) * factorial(y), lst)
Everything works fine, except when I try to pass a string that has only one unique character, i.e. aaa
- I get the wrong answer:
>>>开发者_如何学C; perm('abc')
6
>>> perm('aaa')
2
>>> perm('aaaa')
6
Now, I can tell the problem is in running the lambda function with factorials on a list of length 1. I don't know why, though. Most other lambda functions works on a list of length 1 even if its expecting two elements:
>>> reduce(lambda x,y: x * y, [3])
3
>>> reduce(lambda x,y: x + y, [3])
3
This one doesn't:
>>> reduce(lambda x,y: ord(x) + ord(y), ['a'])
'a'
>>> reduce(lambda x,y: ord(x) + ord(y), ['a','b'])
195
Is there something I should be doing differently? I know I can rewrite the function in many different ways that will circumvent this, (e.g. not using lambda
), but I'm looking for why this specifically doesn't work.
See the documentation for reduce()
, there is an optional 'initializer' argument that is placed before all other elements in the list so that the behavior for one element lists is consistent, for example, for your ord()
lambda you could set initializer
to the the character with an ord()
of 0:
>>> reduce(lambda x, y: ord(x) + ord(y), ['a'], chr(0))
97
Python's reduce
function doesn't always know what the default (initial) value should be. There should be a version that takes an initial value. Supply a sensible initial value and your reduce
should work beautifully.
Also, from the comments, you should probably just use factorial
on the second argument in your lambda:
reduce(lambda x,y: x * factorial(y), lst, 1)
If you want len(s)! / A!*B!*C!
then the use of reduce()
won't work, as it will calculate factorial(factorial(A)*factorial(B))*factorial(C)
. In other words, it really needs the operation to be commutative.
Instead, you'll need to generate the list of factorials, then multiply them together:
import operator
reduce(operator.mul, [factorial(x) for x in lst])
Reduce works by first computing the result for the first two elements in the sequence and then pseudo-recursively follows from there. A list of size 1 is a special case.
I would use a list comprehension here:
prod( [ factorial(val) for val in lst ] )
Good luck!
精彩评论