【问题标题】:Efficiently generate all composite numbers less than N (with their factorizations)有效地生成小于 N 的所有合数(及其分解)
【发布时间】:2012-04-23 23:15:09
【问题描述】:

我想构建一个高效的 Python 迭代器/生成器,它产生:

  • 所有小于 N 的合数
  • 连同它们的素数分解

我称之为“composites_with_factors()”

假设我们已经有一个小于 N 的素数列表,或者一个可以做同样事情的素数生成器。

注意我:

  • 不需要按数字顺序生成数字
  • 不关心是否在开头产生了 1
  • 也不关心是否产生了素数

我认为这可以通过一个聪明的递归生成器来完成......

因此,例如,对 composites_with_factors(16) 的调用可能会产生:

# yields values in form of "composite_value, (factor_tuple)"
2, (2)
4, (2, 2)
8, (2, 2, 2)
6, (2, 3)
12, (2, 2, 3)
10, (2, 5)
14, (2, 7)
3, (3)
9, (3, 3)
15, (3, 5)
5, (5)
7, (7)
11, (11)
13, (13)

正如您从我的输出顺序中看到的那样,我认为这是通过从可用素数生成器上的最小素数开始,并输出该素数的所有幂小于 N 的,然后再尝试通过该素数的幂素数,但在每个阶段看看我是否可以应用额外素数的幂(并且仍然小于 N)。当所有与那个素数的组合都完成后,放下它,然后用素数生成器上可用的下一个最低素数重复。

我尝试用“递归生成器”来做这件事让我很困惑,什么时候用“yield”、“raise StopIteration”或“return”退出递归,或者干脆退出递归函数.

感谢您的智慧!

补充说明:

确实现在有一种方法可以做到这一点:我编写了一个分解数字的函数,因此我可以将它们分解为素数,并产生结果。没问题。我依靠“数字 N 的最低素数是多少”的缓存来保持这一速度极快......对于 N 高达 1000 万。

但是,一旦我从缓存中取出,我们就会将其转移到“幼稚”的因式分解。 (呸。)

这篇文章的重点是:

  • 我假设“从它们的因子生成大型复合材料”将比“分解大型复合材料”更快...尤其是因为我不关心顺序,并且
  • 如何让 Python 生成器“递归”调用自身,并生成一个生成的东西流?

【问题讨论】:

  • 你为这个方法做了哪些努力?请向我们展示您的代码。
  • 您是制作素数生成器,还是只制作奇数生成器?如果你一次只做一件,也许会更容易理解。请向我们展示您目前拥有的代码。
  • @Makoto:我的尝试完全失败了,如果我张贴残骸,也不会发光。例如,我的案例只产生了小于 N 的所有整数的一小部分。
  • 您提到您想递归地执行此操作,但很难击败筛子! en.wikipedia.org/wiki/Sieve_of_Eratosthenes。这可以简单地修改以保留这些因素。
  • @Hooked:好的,我可以再看看 SoE。也许我可以“反转”它以产生复合而不是素数。但是,我曾经尝试实现一个 SoE(用于素数),在我的观察中它占用了大量内存(因为每个阶段都需要“记住”它将过滤掉的内容)。

标签: python algorithm iterator generator


【解决方案1】:

这是一个基于筛子的实现(请原谅非pythonic代码:)):

def sieve(n):
    # start each number off with an empty list of factors
    #   note that nums[n] will give the factors of n
    nums = [[] for x in range(n)]
    # start the counter at the first prime
    prime = 2
    while prime < n:
        power = prime
        while power < n:
            multiple = power
            while multiple < n:
                nums[multiple].append(prime)
                multiple += power
            power *= prime
        # find the next prime
        #   the next number with no factors
        k = prime + 1
        if k >= n:    # no primes left!!!
            return nums
        # the prime will have an empty list of factors
        while len(nums[k]) > 0:
            k += 1
            if k >= n:    # no primes left!!!
                return nums
        prime = k
    return nums


def runTests():
    primes = sieve(100)
    if primes[3] == [3]:
        print "passed"
    else:
        print "failed"
    if primes[10] == [2,5]:
        print "passed"
    else:
        print "failed"
    if primes[32] == [2,2,2,2,2]:
        print "passed"
    else:
        print "failed"

测试:

>>> runTests()
passed
passed
passed

在我的机器上,这需要 56 秒才能运行:

primes = sieve(14000000) # 14 million!

例子:

>>> primes[:10]
[[], [], [2], [3], [2, 2], [5], [2, 3], [7], [2, 2, 2], [3, 3]]

>>> primes[10000]
[2, 2, 2, 2, 5, 5, 5, 5]

>>> primes[65536]
[2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]

>>> primes[6561]
[3, 3, 3, 3, 3, 3, 3, 3]

>>> primes[233223]
[3, 17, 17, 269]

内存消耗:大约 5000 万个整数,在 1400 万个列表中:

>>> sum(map(len, primes))
53303934

【讨论】:

  • 出色的工作。我需要更多地研究筛子,我将以此为例。 (我仍然对内存要求持谨慎态度……但这可能是由于我很久以前 [并且可能很糟糕] 将筛阶段实现为独立对象,每个对象都有自己的内存要求。)
【解决方案2】:

递归(伪代码):

def get_factorizations_of_all_numbers( start = starting_point
                                     , end = end_point
                                     , minp = mimimum_prime
                                     ):
    if start > end:
        return Empty_List
    if minp ^ 2 > end:
        return list_of_all_primes( start, end )
    else
        a = minp * get_factorizations_of_all_numbers( rounddown(start/minp)
                                                    , roundup(end/minp)
                                                    )
        b = get_factorizations_of_all_numbers( start
                                             , end
                                             , next_prime( minp )
                                             )
        return append( a , b )

get_factorizations_of_all_numbers( 1, n, 2 )

【讨论】:

  • 谢谢。在我看来,这个伪代码与@catchmeifyoutry 的实现基本相同。 (至于 minp ^ 2 > end 的测试:我有预感,这只是最微小的优化,因为通过 end=end/minp 的下一次递归“进入”自身将有效地捕捉到相同的条件。)
【解决方案3】:

假设primesiter(n) 会在直到n 的所有素数上创建一个迭代器(primesiter 中不应包含 1,否则以下代码将进入 inf.循环)

def composite_value(n, min_p = 0):
    for p in primesiter(n):
        # avoid double solutions such as (6, [2,3]), and (6, [3,2])
        if p < min_p: continue
        yield (p, [p])
        for t, r in composite_value(n//p, min_p = p): # uses integer division
            yield (t*p, [p] + r)

输出

>> list(composite_value(16))
[(2, [2]),
 (4, [2, 2]),
 (8, [2, 2, 2]),
 (16, [2, 2, 2, 2]),
 (12, [2, 2, 3]),
 (6, [2, 3]),
 (10, [2, 5]),
 (14, [2, 7]),
 (3, [3]),
 (9, [3, 3]),
 (15, [3, 5]),
 (5, [5]),
 (7, [7]),
 (11, [11]),
 (13, [13])]

注意:它也包括 n (= 16),我使用列表而不是元组。如果需要,两者都可以轻松解决,但我将把它留作练习。

【讨论】:

  • 太棒了!那就是让我无法理解的“递归生成器”。我所看到的让我感到困惑:a)我需要在对函数的一次调用中使用两个“for 循环”,b)一个在“内部 for 循环”之前产生,一个在“内部 for 循环”中。
猜你喜欢
  • 2021-04-14
  • 1970-01-01
  • 2011-12-16
  • 2021-08-02
  • 2022-01-03
  • 2014-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多