【问题标题】:Python 2 lists of positive integers finding prime numberPython 2 正整数列表查找素数
【发布时间】:2013-10-20 18:58:31
【问题描述】:

给定 2 个正整数列表,找出有多少种方法可以从每个列表中选择一个数,使它们的和为质数。

我的代码太慢了因为我有 list1 和 list 2,每个都包含 50000 个数字。那么有什么方法可以让它更快,以便在几分钟而不是几天内解决它? :)

    # 2 is the only even prime number
    if n == 2: return True

    # all other even numbers are not primes
    if not n & 1: return False

    # range starts with 3 and only needs to go 
    # up the squareroot of n for all odd numbers
    for x in range(3, int(n**0.5)+1, 2):
        if n % x == 0: return False

    return True



for i2 in l2:
    for i1 in l1:
        if isprime(i1 + i2):
            n = n + 1 # increasing number of ways
            s = "{0:02d}: {1:d}".format(n, i1 + i2)
            print(s) # printing out

【问题讨论】:

  • 这是dynamic programming的工作。
  • 你能帮帮我吗,我是个糟糕的程序员。
  • 整数有多大?
  • 我看不出动态编程技术有什么帮助。这里没有任何可利用类型的明显子结构 - 例如,i1+j1 是否为素数与 i2+j2 是否为素数无关。

标签: python algorithm primes


【解决方案1】:

草图:

  1. 按照@Steve 的建议,首先找出所有素数max(l1) + max(l2)。让我们将该列表称为primes。注意:primes 不需要是列表;您可以改为 generate primes up the max 一次一个。

  2. 交换您的列表(如有必要),使 l2 成为最长的列表。然后把它变成一个集合:l2 = set(l2)

  3. 排序l1 (l1.sort())。

然后:

for p in primes:
    for i in l1:
        diff = p - i
        if diff < 0:
            # assuming there are no negative numbers in l2;
            # since l1 is sorted, all diffs at and beyond this
            # point will be negative
            break
        if diff in l2:
           # print whatever you like
           # at this point, p is a prime, and is the
           # sum of diff (from l2) and i (from l1)

唉,如果l2 是,例如:

l2 = [2, 3, 100000000000000000000000000000000000000000000000000]

这是不切实际的。它依赖于这一点,就像在您的示例中一样,max(max(l1), max(l2)) 是“相当小的”。

充实

嗯!您在评论中说列表中的数字最长为 5 位。所以他们不到100,000。你在一开始就说这个列表每个有 50,000 个元素。所以它们每个都包含大约一半的小于 100,000 的所有可能整数,并且您将有大量的素数和。如果您想进行微优化,这一切都很重要 ;-)

无论如何,由于最大可能的总和小于 200,000,因此 任何 筛分方式都足够快 - 这将是运行时的一个微不足道的部分。以下是其余代码:

def primesum(xs, ys):
    if len(xs) > len(ys):
        xs, ys = ys, xs
    # Now xs is the shorter list.
    xs = sorted(xs)  # don't mutate the input list
    sum_limit = xs[-1] + max(ys)  # largest possible sum
    ys = set(ys)     # make lookups fast
    count = 0
    for p in gen_primes_through(sum_limit):
        for x in xs:
            diff = p - x
            if diff < 0:
                # Since xs is sorted, all diffs at and
                # beyond this point are negative too.
                # Since ys contains no negative integers,
                # no point continuing with this p.
                break
            if diff in ys:
                #print("%s + %s = prime %s" % (x, diff, p))
                count += 1
    return count

我不会提供我的gen_primes_through(),因为这无关紧要。从其他答案中选择一个,或者自己写。

这是提供测试用例的便捷方式:

from random import sample
xs = sample(range(100000), 50000)
ys = sample(range(100000), 50000)
print(primesum(xs, ys))

注意:我使用的是 Python 3。如果您使用的是 Python 2,请使用 xrange() 而不是 range()

在两次运行中,每次运行大约需要 3.5 分钟。这就是您一开始就要求的(“分钟而不是天”)。 Python 2 可能会更快。返回的计数是:

219,334,097

219,457,533

可能的总数当然是 50000**2 == 2,500,000,000。

关于时间

所有这里讨论的方法,包括您的原始方法,所花费的时间与两个列表长度的乘积成正比。所有的摆弄都是为了减少常数因子。与原来的相比,这是一个巨大的改进:

def primesum2(xs, ys):
    sum_limit = max(xs) + max(ys)  # largest possible sum
    count = 0
    primes = set(gen_primes_through(sum_limit))
    for i in xs:
        for j in ys:
            if i+j in primes:
                # print("%s + %s = prime %s" % (i, j, i+j))
                count += 1
    return count

也许你会更好地理解这一点。为什么会有很大的改进?因为它用极快的集合查找取代了昂贵的isprime(n) 函数。它仍然需要与len(xs) * len(ys) 成比例的时间,但是通过用非常便宜的操作替换非常昂贵的内循环操作,“比例常数”被削减了。

事实上,primesum2() 在很多情况下也比我的primesum() 快。是什么让primesum()您的特定情况中更快的是,只有大约 18,000 个质数小于 200,000。因此,迭代素数(如 primesum() 所做的那样)比迭代具有 50,000 个元素的列表要快得多。

针对此问题的“快速”通用函数需要根据输入选择不同的方法。

【讨论】:

  • 不,我有一个不同号码的预制列表。我需要找出有多少种方法可以从每个列表中选择一个数字,以使它们的总和是素数。
  • @WinCd,是的,我明白这一点。你显然不明白,我,虽然 :-( 抱歉,但我现在没时间了 - 试着再读一遍?如果仍然不清楚,请非常明确地说明你不明白的部分.
  • @WinCd,看看我刚才的编辑。没有比完整显示代码更清楚了 ;-)
  • 上面发布的代码在 20 小时内完成了您的解决方案在
  • 对不起,我不知道如何给这里的任何人发信息。您“应该”调试的是有两种不同的算法,并在许多 small 输入上运行它们。当他们不同意某个结果时,您可以轻松调查原因。没有人能够通过盯着包含 50,000 个元素的列表来猜测。
【解决方案2】:

我会在每个范围内找到最大的数字。素数的范围是最大数之和。

这是筛选素数的代码:

def eras(n):
    last = n + 1
    sieve = [0, 0] + list(range(2, last))
    sqn = int(round(n ** 0.5))
    it = (i for i in xrange(2, sqn + 1) if sieve[i])
    for i in it:
        sieve[i * i:last:i] = [0] * (n // i - i + 1)
    return filter(None, sieve)

找到最多 10 000 000 个素数大约需要 3 秒。然后我会使用与生成总和相同的 n ^ 2 算法。我认为有一个n logn 算法,但我想不出它。

看起来像这样:

from collections import defaultdict
possible = defaultdict(int)
for x in range1:
    for y in range2:
        possible[x + y] += 1

def eras(n):
    last = n + 1
    sieve = [0, 0] + list(range(2, last))
    sqn = int(round(n ** 0.5))
    it = (i for i in xrange(2, sqn + 1) if sieve[i])
    for i in it:
        sieve[i * i:last:i] = [0] * (n // i - i + 1)
    return filter(None, sieve)

n = max(possible.keys())
primes = eras(n)
possible_primes = set(possible.keys()).intersection(set(primes))

for p in possible_primes:
    print "{0}: {1} possible ways".format(p, possible[p])

【讨论】:

  • 我的列表中包含不连续的随机数,例如 10,20,30 它更像是 199,250,986
  • @Win Cd:没关系。找出每个数字范围内的最大值。生成可能的总和(n 平方计算)。生成所需的最大值(每个范围内的最大值之和)的素数。将这两组相交。
  • @hughdbrown,一种优雅的方法!它的运行速度比我在“他的那种”测试数据上发布的慢大约 8 倍(两个列表,每个列表包含来自 range(100000) 的 50000 个整数的随机样本),但在不太极端的情况下比我的要快。你的n log n 直觉有什么收获吗?那将是真正的改进:-)
【解决方案3】:

您应该使用Sieve of Eratosthenes 来计算素数。

您还在计算每个可能的总和组合的素数。相反,请考虑使用列表中的总和找到您可以达到的最大值。生成直到最大值的所有素数的列表。

在将数字相加时,您可以查看该数字是否出现在您的素数列表中。

【讨论】:

    猜你喜欢
    • 2011-01-30
    • 1970-01-01
    • 2018-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多