带有重复的非素因子分解
假设我们有一些数字因子,比如1260:
>>> factors(1260)
[2, 2, 3, 3, 5, 7]
在Python中,如何找到这些数字的所有组合,得到每一种可能的乘积,也就是所有的因式分解,而不仅仅是质因数分解,并且这些因子的和要小于最大乘积呢?
如果我从质因数开始组合,我还得重新分解剩下的部分,因为我不知道哪些部分没有被组合在一起。
我也可以改进我的因子函数,让它生成因子的配对,而不是按大小顺序排列,但这样做对于最大乘积达到12000的数字来说,还是会很费劲。乘积必须始终保持不变。
我曾经参考过一个因子例程,但觉得为了适应我的其他代码而做的努力不值得。至少我的因子函数比sympy的快很多:
def divides(number):
if number<2:
yield number
return
high = [number]
sqr = int(number ** 0.5)
limit = sqr+(sqr*sqr != number)
yield 1
for divisor in xrange(3, limit, 2) if (number & 1) else xrange(2, limit):
if not number % divisor:
yield divisor
high.append(number//divisor)
if sqr*sqr== number: yield sqr
for divisor in reversed(high):
yield divisor
重新使用这段代码唯一的问题是要把因子和分解筛联系起来,或者做某种形式的itertools.product,把因子的因子配对输出,而不是排序。
示例结果可能是:
[4, 3, 3, 5, 7] (one replacement of two)
[5, 7, 36] (one replacement of three)
[3, 6, 14, 5] (two replacements)
我可能需要某种方法来生成筛或动态规划解决方案,以便为较小的因子提供链接到它们的数字。不过,避免重叠看起来很困难。我确实有一个筛选函数,它为每个数字存储最大的质因子,以加快分解速度,而不需要保存每个数字的完整因式分解……也许可以进行调整。
更新:因子的和应该接近乘积,所以答案中可能有很多因子≤10(最多14个因子)。
更新2: 这是我的代码,但我必须弄清楚如何递归或迭代地进行多次移除,以处理长度大于2的部分,并深入挖掘词法分区,以替换产生重复的跳跃位模式(仅一个替换的命中计数很可怜,而且这还不包括在单一分区内的“单元素分区”的传递):
from __future__ import print_function
import itertools
import operator
from euler import factors
def subset(seq, mask):
""" binary mask of len(seq) bits, return generator for the sequence """
# this is not lexical order, replace with lexical order masked passing duplicates
return (c for ind,c in enumerate(seq) if mask & (1<<ind))
def single_partition(seq, n = 0, func = lambda x: x):
''' map given function to one partition '''
for n in range(n, (2**len(seq))):
result = tuple(subset(seq,n))
others = tuple(subset(seq,~n))
if len(result) < 2 or len(others) == 0:
#empty subset or only one or all
continue
result = (func(result),)+others
yield result
if __name__=='__main__':
seen, hits, count = set(), 0, 0
for f in single_partition(factors(13824), func = lambda x: reduce(operator.mul, x)):
if f not in seen:
print(f,end=' ')
seen.add(f)
else:
hits += 1
count += 1
print('\nGenerated %i, hits %i' %(count,hits))
改进 我很高兴只得到最多5个因子的非质因子部分的因式分解。我手动发现,最多5个相同因子的非递减排列遵循这种形式:
partitions of 5 applied to 2**5
1 1 1 1 1 2 2 2 2 2
1 1 1 2 2 2 2 4
1 1 1 3 2 2 8
1 2 2 2 4 4
1 4 2 16
2 3 4 8
解决方案 我不想删除被接受的答案,因为它的解决方案太复杂了。从Project Euler中,我只揭示了这个来自NZ的orbifold的辅助函数,它运行得更快,而且不需要先找到质因子:
def factorings(n,k=2):
result = []
while k*k <= n:
if n%k == 0:
result.extend([[k]+f for f in factorings(n/k,k)])
k += 1
return result + [[n]]
与他在Python 2.7中运行的问题88的相关解决方案,根据我的计时装饰器,耗时4.85秒,经过优化停止条件后,找到的计数器在2.6.6中为3.4秒,使用psyco,在2.7中没有psyco为3.7秒。我自己的代码从接受答案中的代码(我移除了排序)耗时30秒,缩短到2.25秒(2.7没有psyco),在Python 2.6.6中使用psyco为782毫秒。
3 个回答
from __future__ import print_function
import itertools
import operator
def partition(iterable, chain=itertools.chain, map=map):
# http://code.activestate.com/recipes/576795/
# In [1]: list(partition('abcd'))
# Out[1]:
# [['abcd'],
# ['a', 'bcd'],
# ['ab', 'cd'],
# ['abc', 'd'],
# ['a', 'b', 'cd'],
# ['a', 'bc', 'd'],
# ['ab', 'c', 'd'],
# ['a', 'b', 'c', 'd']]
s = iterable if hasattr(iterable, '__getslice__') else tuple(iterable)
n = len(s)
first, middle, last = [0], range(1, n), [n]
getslice = s.__getslice__
return [map(getslice, chain(first, div), chain(div, last))
for i in range(n) for div in itertools.combinations(middle, i)]
def product(factors,mul=operator.mul):
return reduce(mul,factors,1)
def factorings(factors,product=product,
permutations=itertools.permutations,
imap=itertools.imap,
chain_from_iterable=itertools.chain.from_iterable,
):
seen=set()
seen.add(tuple([product(factors)]))
for grouping in chain_from_iterable(
imap(
partition,
set(permutations(factors,len(factors)))
)):
result=tuple(sorted(product(group) for group in grouping))
if result in seen:
continue
else:
seen.add(result)
yield result
if __name__=='__main__':
for f in factorings([2,2,3,3,5,7]):
print(f,end=' ')
(3, 420) (9, 140) (28, 45) (14, 90) (2, 630) (3, 3, 140) (3, 15, 28) (3, 14, 30) (2, 3, 210) (5, 9, 28) (9, 10, 14) (2, 9, 70) (2, 14, 45) (2, 7, 90) (3, 3, 5, 28) (3, 3, 10, 14) (2, 3, 3, 70) (2, 3, 14, 15) (2, 3, 7, 30) (2, 5, 9, 14) (2, 7, 9, 10) (2, 2, 7, 45) (2, 3, 3, 5, 14) (2, 3, 3, 7, 10) (2, 2, 3, 7, 15) (2, 2, 5, 7, 9) (2, 2, 3, 3, 5, 7) (5, 252) (10, 126) (18, 70) (6, 210) (2, 5, 126) (5, 14, 18) (5, 6, 42) (7, 10, 18) (6, 10, 21) (2, 10, 63) (3, 6, 70) (2, 5, 7, 18) (2, 5, 6, 21) (2, 2, 5, 63) (3, 5, 6, 14) (2, 3, 5, 42) (3, 6, 7, 10) (2, 3, 10, 21) (2, 3, 5, 6, 7) (2, 2, 3, 5, 21) (4, 315) (20, 63) (2, 2, 315) (4, 5, 63) (4, 9, 35) (3, 4, 105) (7, 9, 20) (3, 20, 21) (2, 2, 9, 35) (2, 2, 3, 105) (4, 5, 7, 9) (3, 4, 5, 21) (3, 3, 4, 35) (3, 3, 7, 20) (2, 2, 3, 3, 35) (3, 3, 4, 5, 7) (7, 180) (3, 7, 60) (2, 18, 35) (2, 6, 105) (3, 10, 42) (2, 3, 6, 35) (15, 84) (12, 105) (3, 5, 84) (5, 12, 21) (7, 12, 15) (4, 15, 21) (2, 15, 42) (3, 5, 7, 12) (3, 4, 7, 15) (2, 6, 7, 15) (2, 2, 15, 21) (21, 60) (30, 42) (6, 7, 30) (5, 7, 36) (2, 21, 30) (5, 6, 6, 7) (3, 12, 35) (6, 14, 15) (4, 7, 45) (35, 36) (6, 6, 35)
产生
你想要找的东西通常被称为因数。这个回答可以帮助你理解这个问题。
我使用一个列表,比如 [(2, 9), (3, 3)]
,这个列表代表了未展开的因子的基础列表,最终会变成 [2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3]
。在每一轮中,我会从基础列表中选一个因子,减少它的数量,然后:
- 把它加到已展开的列表中,这样已展开的列表就多了一个因子(直到达到设定的限制)
- 把这个因子和所有已展开的因子相乘,生成所有可能的组合
通过动态规划和设定限制,这个过程变得非常快:
from itertools import groupby
def multiplicies( factors ):
""" [x,x,x,,y,y] -> [(x,3), (y,2)] """
return ((k, sum(1 for _ in group)) for k, group in groupby(factors))
def combinate(facs, cutoff=None):
facs = tuple(multiplicies(facs))
results = set()
def explode(base, expanded):
# `k` is the key for the caching
# if the function got called like this before return instantly
k = (base, expanded)
if k in results:
return
results.add(k)
# pick a factor
for (f,m) in base:
# remove it from the bases
newb = ((g, (n if g!=f else n-1)) for g,n in base)
newb = tuple((g,x) for g,x in newb if x > 0)
# do we cutoff yet?
if cutoff is None or len(newb) + len(expanded) < cutoff:
explode(newb, tuple(sorted(expanded + (f,))))
# multiply the pick with each factor in expanded
for pos in range(len(expanded)):
newexp = list(expanded)
newexp[pos] *= f
explode(newb, tuple(sorted(newexp)))
explode(facs, ())
# turn the `k` (see above) into real factor lists
return set((tuple((x**y) for x,y in bases) + expanded)
for (bases, expanded) in results)
# you dont even need the cutoff here!
combinate([2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3])
# but you need it for
combinate([2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3,5,7,9,11], 5)
如果可以的话,试试Psyco(我这里只能用Py2.7,所以我不能试),它可能会让这个过程更快一些。