开发者

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.

I'm writing the method like this:

(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!

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜