优雅的方式来跳过迭代中的元素

时间:2013-05-28 20:23:10

标签: python iterator iterable

我有一个很大的可迭代,实际上是一个很大的迭代:

itertools.permutations(range(10))

我想访问第一百万个元素。我已经以某种不同的方式解决了问题。

  1. 将iterable转换为list并获取1000000th元素:

    return list(permutations(range(10)))[999999]
    
  2. 手动滑动元素直到999999:

    p = permutations(range(10))
    for i in xrange(999999): p.next()
    return p.next()
    
  3. 手动滑动元素v2:

    p = permutations(range(10))
    for i, element in enumerate(p):
        if i == 999999:
            return element
    
  4. 使用来自itertools的islice:

    return islice(permutations(range(10)), 999999, 1000000).next()
    
  5. 但我仍然觉得这些都不是python的优雅方式。第一个选项太昂贵,它需要计算整个迭代只是为了访问单个元素。如果我没有错,islice在内部执行的方法与我在方法2中所做的相同,并且几乎完全是第3次,也许它有更多的冗余操作。

    所以,我只是好奇,想知道在python中是否有其他方式可以访问迭代的具体元素,或者至少以更优雅的方式跳过第一个元素,或者如果我只是需要使用上述之一。

3 个答案:

答案 0 :(得分:15)

使用itertools recipe consume跳过n元素:

def consume(iterator, n):
    "Advance the iterator n-steps ahead. If n is none, consume entirely."
    # Use functions that consume iterators at C speed.
    if n is None:
        # feed the entire iterator into a zero-length deque
        collections.deque(iterator, maxlen=0)
    else:
        # advance to the empty slice starting at position n
        next(islice(iterator, n, n), None)

注意那里的islice()电话;它使用n, n,实际上没有返回任何next()函数会回退到默认值。

简化为您想要跳过999999个元素的示例,然后返回元素1000000:

return next(islice(permutations(range(10)), 999999, 1000000))

islice()处理C中的迭代器,这是Python循环无法击败的东西。

为了说明,以下是每种方法仅重复10次的时间:

>>> from itertools import islice, permutations
>>> from timeit import timeit
>>> def list_index():
...     return list(permutations(range(10)))[999999]
... 
>>> def for_loop():
...     p = permutations(range(10))
...     for i in xrange(999999): p.next()
...     return p.next()
... 
>>> def enumerate_loop():
...     p = permutations(range(10))
...     for i, element in enumerate(p):
...         if i == 999999:
...             return element
... 
>>> def islice_next():
...     return next(islice(permutations(range(10)), 999999, 1000000))
... 
>>> timeit('f()', 'from __main__ import list_index as f', number=10)
5.550895929336548
>>> timeit('f()', 'from __main__ import for_loop as f', number=10)
1.6166789531707764
>>> timeit('f()', 'from __main__ import enumerate_loop as f', number=10)
1.2498459815979004
>>> timeit('f()', 'from __main__ import islice_next as f', number=10)
0.18969106674194336

islice()方法比下一个最快的方法快近7倍。

答案 1 :(得分:4)

找到第n个排列可能只是一个例子,但如果这实际上是你试图解决的问题,那么有一个更好的方法来做到这一点。您可以直接计算第n个排列,而不是跳过迭代的元素。借用another answer here中的代码:

import math

def nthperm(li, n):
    li = list(li)
    n -= 1
    s = len(li)
    res = []
    if math.factorial(s) <= n:
        return None
    for x in range(s-1,-1,-1):
        f = math.factorial(x)
        d = n / f
        n -= d * f
        res.append(li[d])
        del(li[d])
    return res

示例和时序比较:

In [4]: nthperm(range(10), 1000000)
Out[4]: [2, 7, 8, 3, 9, 1, 5, 4, 6, 0]

In [5]: next(islice(permutations(range(10)), 999999, 1000000))
Out[5]: (2, 7, 8, 3, 9, 1, 5, 4, 6, 0)

In [6]: %timeit nthperm(range(10), 1000000)
100000 loops, best of 3: 9.01 us per loop

In [7]: %timeit next(islice(permutations(range(10)), 999999, 1000000))
10 loops, best of 3: 29.5 ms per loop

答案相同,快了3000多倍。请注意,我确实对原始代码稍作修改,以便它不再破坏原始列表。

答案 2 :(得分:2)

为了达到下一个项目,砸掉一百万件物品确实是非常浪费的。不幸的是,它是否可以避免取决于你的迭代器:如果迭代器有一种方法可以直接跳到特定的偏移量,它可以实现__getitem__方法,你可以用它来直接请求iterator[1000000]。 (如何实现这一点取决于生成算法)。

如果您的数据源需要生成所有先前的值才能到达那里,那么将它们丢弃的方法是您遇到的问题最少。你可以选择一个不错的方式,但它只是锦上添花。

PS。鉴于你的问题的背景,我将概述一个直接生成第n个排列的算法,但我看到@ F.J。打败了我。好的解决方案: - )