在Python中获取生成器的第n个项


70

有没有一种语法更简洁的方式编写以下内容?

gen = (i for i in xrange(10))
index = 5
for i, v in enumerate(gen):
    if i is index:
        return v

生成器应该具有一个gen[index]表达式,该表达式充当列表,但在功能上与上述代码相同,这似乎是很自然的。


12
您不希望is在这种情况下(或根本没有很多情况)。is是用于比较身份,而不是平等。你要==。在这种情况下,这可能会起作用,但只能通过巧合和实现细节来实现。
Mike Graham 2010年

1
由于我使用的是整数,它怎么可能不起作用?在这种情况下,期望index对象实现甚至是一种好习惯__eq__?(这是下车的话题...)
Oliver Zheng

2
尝试一下1000 is 500 + 500,它将(可能)是False。见,例如,stackoverflow.com/questions/306313/...
斯科特·格里菲思

2
为此问题+1。说“ gen的第n个结果”没有那么冗长的方式似乎很奇怪。
LarsH

另一种可能性是拉链---它们处理任意树,但列表也是树。参见此实现github.com/trivio/zipper/blob/master/tests/test_zipper.py
Reb.Cabin

Answers:


70

一种方法是使用 itertools.islice

>>> gen = (x for x in range(10))
>>> index = 5
>>> next(itertools.islice(gen, index, None))
5

15

您可以使用count示例生成器来做到这一点:

from itertools import islice, count
next(islice(count(), n, n+1))

这是什么版本的Python?上面的代码给了我AttributeError: 'itertools.islice' object has no attribute 'next'3.3中的错误。
LarsH

在Python 3x中,更改next__next__(),即islice(count, n, n=1).__next__()
Mohammed

2
因此,最好使用next(islice(count(), n, n+1))
Frozen Flame》

我认为您可以摆脱上限,即next(islice(count(), n, None))
user76284

7

我认为最好的方法是:

next(x for i,x in enumerate(it) if i==n)

it您的迭代器在哪里n,索引在哪里)

它不需要您添加导入(例如使用的解决方案itertools),也不需要一次将迭代器的所有元素加载到内存中(例如使用的解决方案list)。

注意1:StopIteration如果您的迭代器的项目少于n个,则此版本将引发错误。如果您想None取而代之,可以使用:

next((x for i,x in enumerate(it) if i==n), None)

注意2:对的调用中没有方括号next。这不是列表理解,而是生成器理解,它不消耗原始迭代器的第n个元素。


我相信这会遍历整个迭代器,这在迭代器需要很长时间才能完成时使其变慢。
ubershmekel '19

1
@ubershmekel:不,不会!它将迭代前n个元素(当然),仅此而已。为什么不自己尝试呢?
lovasoa

1
我在第二个注释中添加了一个注释,以表明原始迭代器没有被完全使用
lovasoa

5

我反对将生成器视为列表的诱惑。简单但幼稚的方法是简单的一线:

gen = (i for i in range(10))
list(gen)[3]

但是请记住,生成器不像列表。他们不会将中间结果存储在任何地方,因此您不能倒退。我将在python repl中用一个简单的例子演示这个问题:

>>> gen = (i for i in range(10))
>>> list(gen)[3]
3
>>> list(gen)[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

一旦开始通过生成器获取序列中的第n个值,生成器现在处于不同的状态,再次尝试获取第n个值将返回不同的结果,这很可能会导致您的错误码。

让我们看看另一个示例,它基于问题中的代码。

最初,人们会期望以下内容打印4两次。

gen = (i for i in range(10))
index = 4
for i, v in enumerate(gen):
    if i == index:
        answer = v
        break
print(answer)
for i, v in enumerate(gen):
    if i == index:
        answer = v
        break
print(answer)

但是将其输入到repl中,您将得到:

>>> gen = (i for i in range(10))
>>> index = 4
>>> for i, v in enumerate(gen):
...     if i == index:
...             answer = v
...             break
... 
>>> print(answer)
4
>>> for i, v in enumerate(gen):
...     if i == index:
...             answer = v
...             break
... 
>>> print(answer)
9

祝您早日找到该错误。

编辑:

如前所述,如果生成器无限长,则您甚至无法将其转换为列表。表达式list(gen)永远不会结束。

您可以通过一种方法将延迟评估的缓存包装器放在无限生成器周围,使它看起来像可以随意索引的无限长列表,但这值得它自己的问题和答案,并且会对性能产生重大影响。


5
如果生成器是无限的怎么办?
尼迪尔

2
这应该更高一些,因为这样做会花费大量时间。感谢您指出这一点。
ZdWhite

0

我想到的第一件事是:

gen = (i for i in xrange(10))
index = 5

for i, v in zip(range(index), gen): pass

return v

0

如果n在创作时已知,则可以使用解构。例如获得第三项:

>>> [_, _, third, *rest] = range(10)
>>> third
2
>>> rest
[3, 4, 5, 6, 7, 8, 9]

-1

最好使用的是: 示例:

a = gen values ('a','c','d','e')

因此答案将是:

a = list(a) -> this will convert the generator to a list (it will store in memory)

然后,当您要查找特定索引时,您将:

a[INDEX] -> and you will able to get the value its holds 

如果您只想知道计数或执行不需要在内存中存储的操作,最佳实践将是: a = sum(1 in i in a)->这将计算您拥有的对象数

希望我让它更简单。


-2

也许您应该详细说明实际的用例。

>>> gen = xrange(10)
>>> ind=5 
>>> gen[ind]
5

4
我编辑xrange(10)(i for i in xrange(10))。事实证明,此语法适用,xrange因为它并不是真正的生成器……
Oliver Zheng

5
xrange在生成器之前,并返回一个xrange对象,该对象实际上实现了完整序列协议。
Mike Graham 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.