在lambda函数中使用math.factorial和reduce()

1 投票
4 回答
1729 浏览
提问于 2025-04-17 03:07

我正在尝试写一个函数,用来计算一个字符串的不同排列组合的数量。例如,字符串 aaa 应该返回 1,而 abc 应该返回 6
我写这个方法是这样的:

(伪代码:)

len(string)! / (A!*B!*C!*...) 

这里的 A、B、C 是每个独特字符出现的次数。比如,字符串 'aaa' 的计算方式是 3! / 3! = 1,而 'abc' 的计算方式是 3! / (1! * 1! * 1!) = 6

到目前为止,我的代码是这样的:

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)

一切都运行得很好,除了当我尝试传入一个只有一个独特字符的字符串,比如 aaa 时,我得到了错误的答案:

>>> perm('abc')
6
>>> perm('aaa')
2
>>> perm('aaaa')
6

现在,我知道问题出在用阶乘计算的 lambda 函数在长度为 1 的列表上运行时。我不太明白为什么。不过,大多数其他的 lambda 函数在长度为 1 的列表上也能正常工作,即使它们期望有两个元素:

>>> reduce(lambda x,y: x * y, [3])
3
>>> reduce(lambda x,y: x + y, [3])
3

但这个就不行:

>>> reduce(lambda x,y: ord(x) + ord(y), ['a'])
'a' 
>>> reduce(lambda x,y: ord(x) + ord(y), ['a','b'])
195

我是不是应该以不同的方式来处理这个问题?我知道我可以用很多不同的方法重写这个函数来避免这个问题(例如,不使用 lambda),但我想知道为什么这个特定的情况不工作。

4 个回答

1

如果你想要计算 len(s)! / A!*B!*C!,那么使用 reduce() 是不行的,因为它会计算 factorial(factorial(A)*factorial(B))*factorial(C)。换句话说,这个操作需要是可交换的,也就是顺序不影响结果。

相反,你需要先生成一个阶乘的列表,然后把它们相乘起来:

import operator
reduce(operator.mul, [factorial(x) for x in lst])
2

查看关于 reduce() 的文档,里面有一个可选的 'initializer' 参数,它会放在列表中所有其他元素之前,这样对于只有一个元素的列表,行为就会保持一致。例如,对于你的 ord() 函数,可以把 initializer 设置为一个字符,它的 ord() 值是 0:

>>> reduce(lambda x, y: ord(x) + ord(y), ['a'], chr(0))
97
1

Python的reduce函数有时候不知道默认的初始值应该是什么。应该有一个版本可以让你自己指定初始值。只要提供一个合适的初始值,你的reduce就能很好地工作。

另外,从评论来看,你可能只需要在你的lambda表达式的第二个参数上使用factorial函数:

reduce(lambda x,y: x * factorial(y), lst, 1)

撰写回答