Python:加速从列表中删除每个第n个元素

时间:2010-03-18 22:14:02

标签: python performance algorithm

我正在尝试解决this programming riddle,虽然解决方案(请参见下面的代码)正常工作,但成功提交速度太慢了。

  • 有关如何进行此操作的任何指示 更快(从列表中删除每个第n个元素)?
  • 或建议更好的算法来计算相同的;好像我什么都想不到 除了现在的蛮力......

基本上,手头的任务是:

GIVEN:
L = [2,3,4,5,6,7,8,9,10,11,........]
1. Take the first remaining item in list L (in the general case 'n'). Move it to 
   the 'lucky number list'. Then drop every 'n-th' item from the list.
2. Repeat 1

TASK:
Calculate the n-th number from the 'lucky number list' ( 1 <= n <= 3000)

我的原始代码(它在我的机器上计算了大约一秒钟内的3000个第一个幸运数字 - 不幸的是太慢了):

"""
SPOJ Problem Set (classical) 1798. Assistance Required
URL: http://www.spoj.pl/problems/ASSIST/
"""

sieve = range(3, 33900, 2)
luckynumbers = [2]

while True:
    wanted_n = input()
    if wanted_n == 0:
        break

    while len(luckynumbers) < wanted_n:
        item = sieve[0]
        luckynumbers.append(item)
        items_to_delete = set(sieve[::item])
        sieve = filter(lambda x: x not in items_to_delete, sieve)
    print luckynumbers[wanted_n-1]

编辑:感谢 Mark Dickinson,Steve Jessop和gnibbler的出色贡献,我得到了以下内容,这比我原来的代码快得多(并且成功地在{ {3}} 0.58秒!)......

sieve = range(3, 33810, 2)
luckynumbers = [2]

while len(luckynumbers) < 3000:
    if len(sieve) < sieve[0]:
        luckynumbers.extend(sieve)
        break
    luckynumbers.append(sieve[0])
    del sieve[::sieve[0]]

while True:
    wanted_n = input()
    if wanted_n == 0:
        break
    else:
        print luckynumbers[wanted_n-1]

5 个答案:

答案 0 :(得分:7)

此系列称为 ludic numbers

__delslice__应该比__setslice__ + filter

更快
>>> L=[2,3,4,5,6,7,8,9,10,11,12]
>>> lucky=[]
>>> lucky.append(L[0])
>>> del L[::L[0]]
>>> L
[3, 5, 7, 9, 11]
>>> lucky.append(L[0])
>>> del L[::L[0]]
>>> L
[5, 7, 11]

所以循环变为。

while len(luckynumbers) < 3000:
    item = sieve[0]
    luckynumbers.append(item)
    del sieve[::item] 

在不到0.1秒的时间内运行

答案 1 :(得分:4)

尝试使用这两行进行删除和过滤,而不是使用它们; filter(None, ...)的运行速度比filter(lambda ...)快得多。

sieve[::item] = [0]*-(-len(sieve)//item)
sieve = filter(None, sieve)

编辑:更好地使用del sieve[::item];见gnibbler的解决方案。

您也可以为while循环找到更好的终止条件:例如,如果筛子中的第一个剩余项目是i,那么筛子的第一个i元素将变为下一个i个幸运数字;所以如果len(luckynumbers) + sieve[0] >= wanted_n你应该已经计算出你需要的数字了 - 你只需要确定它在sieve中的位置,以便你可以提取它。

在我的机器上,以下版本的内循环运行速度比原始版本快15倍,以找到第3000个幸运数字:

while len(luckynumbers) + sieve[0] < wanted_n:
    item = sieve[0]
    luckynumbers.append(item)
    sieve[::item] = [0]*-(-len(sieve)//item)
    sieve = filter(None, sieve)
print (luckynumbers + sieve)[wanted_n-1]

答案 2 :(得分:2)

可以找到有关如何解决此问题的说明here。 (我链接的问题要求更多,但该问题的主要步骤与您尝试解决的问题相同。)我链接的站点也包含C ++中的示例解决方案。

这组数字可以用二叉树表示,它支持以下操作:

  • 返回n元素
  • 删除n元素

可以实现这些操作以在O(log n)时间运行,其中n是树中节点的数量。

要构建树,您可以创建一个自定义例程,从给定的元素数组构建树,或者实现插入操作(确保树保持平衡)。

树中的每个节点都需要以下信息:

  • 指向左右儿童的指示
  • 左右子树中有多少项

有了这样的结构,解决问题的其余部分应该相当简单。

我还建议在读取任何输入之前计算所有可能输入值的答案,而不是计算每个输入行的答案。

上述算法的Java实现在您链接的网站上在0.68秒内被接受。

(很抱歉没有提供任何特定于Python的帮助,但希望上面列出的算法足够快。)

答案 3 :(得分:1)

最好使用数组并使用该策略清空每个第N个项目;在连续几次执行此操作后,更新开始变得棘手,因此您需要重新构建阵列。这应该将速度提高至少10倍。你需要比这更好吗?

答案 4 :(得分:0)

为什么不创建一个新列表?

L = [x for (i, x) in enumerate(L) if i % n]
相关问题