我有一个很大的可迭代,实际上是一个很大的迭代:
itertools.permutations(range(10))
我想访问第一百万个元素。我已经以某种不同的方式解决了问题。
将iterable转换为list并获取1000000th元素:
return list(permutations(range(10)))[999999]
手动滑动元素直到999999:
p = permutations(range(10))
for i in xrange(999999): p.next()
return p.next()
手动滑动元素v2:
p = permutations(range(10))
for i, element in enumerate(p):
if i == 999999:
return element
使用来自itertools的islice:
return islice(permutations(range(10)), 999999, 1000000).next()
但我仍然觉得这些都不是python的优雅方式。第一个选项太昂贵,它需要计算整个迭代只是为了访问单个元素。如果我没有错,islice在内部执行的方法与我在方法2中所做的相同,并且几乎完全是第3次,也许它有更多的冗余操作。
所以,我只是好奇,想知道在python中是否有其他方式可以访问迭代的具体元素,或者至少以更优雅的方式跳过第一个元素,或者如果我只是需要使用上述之一。
答案 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。打败了我。好的解决方案: - )