带有重复的非素因子分解

1 投票
3 回答
564 浏览
提问于 2025-04-16 13:04

假设我们有一些数字因子,比如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 个回答

1
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)

产生

1

你想要找的东西通常被称为因数。这个回答可以帮助你理解这个问题

1

我使用一个列表,比如 [(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,所以我不能试),它可能会让这个过程更快一些。

撰写回答