如何逐次分别迭代多个生成器

2017-08-24 16:26:11 +08:00
 billion

我想先从第一个生成器取第 1 个值,从第二个生成器取第 1 个值,从第三个生成器取第 1 个值

接下来从第一个生成器取第 2 个值,从第二个生成器取第 2 个值,从第三个生成器取第 2 个值

以此类推,

最后从第一个生成器取第 5 个值,从第二个生成器取第 5 个值,从第三个生成器取第 5 个值

a = (x for x in range(1, 6))
b = (x for x in range(6, 11))
c = (x for x in range(11, 16))

d = (x for x in [a, b, c])

def y():
    for m in d:
        yield next(m)
for i in y():
    print(i)

我想实现的输出为:

1
6
11
2
7
12
3
8
13
4
9
14
5
10
15

但是,上面的代码由于生成器只能被完整迭代一次所以在 for m in d:这个位置就会出问题。最后只能得到 1, 6, 11

请问有什么比较好的办法解决实现这个需求吗?

这个问题,是为了实现逐行对比超大 Log。我想一行一行对比 Log,但是由于三个 Log 各自都超过了 40G,因此想通过生成器的这种方式来实现。

2295 次点击
所在节点    Python
25 条回复
wwqgtxx
2017-08-24 16:47:15 +08:00
a = range(1,6)
b = range(6,11)
c = range(11,16)

def y():
----while True:
--------yield a.__next__()
--------yield b.__next__()
--------yield c.__next__()

for i in y():
----print(i)
billion
2017-08-24 16:53:41 +08:00
可以正常运行,非常感谢。
billion
2017-08-24 16:55:14 +08:00
@wwqgtxx 但如果有非常多的文件呢,比如有一万个文件,那在 while True 没有办法分别单独 yield xxx.__next__()了。此时又怎么办?
fanhaipeng0403
2017-08-24 16:58:26 +08:00
In [21]: a = iter(range(1,6))
...: b = iter(range(6,11))
...: c = iter(range(11,16))
...:
...:
...:
...:

In [22]: def y():
...: while 1:
...: yield next(a)
...: yield next(b)
...: yield next(c)
...:

In [23]:

In [23]: for i in y():
...: print(i)
...:
1
6
11
2
7
12
3
8
13
4
9
14
5
10
15
wwqgtxx
2017-08-24 16:59:24 +08:00
>>> a = range(1,6)
>>> b = range(6,11)
>>> c = range(11,16)
>>>
>>> d = [a.__iter__(),b.__iter__(),c.__iter__()]
>>>
>>> def y():
... while True:
... for m in d:
... yield next(m)
...
>>> for i in y():
... print(i)
...
fanhaipeng0403
2017-08-24 16:59:59 +08:00
为什么我的显示 -。-AttributeError: 'range' object has no attribute '__next__'
wwqgtxx
2017-08-24 17:00:30 +08:00
a = range(1,6)
b = range(6,11)
c = range(11,16)

d = [iter(a),iter(b),iter(c)]

def y():
----while True:
--------for m in d:
------------yield next(m)

for i in y():
----print(i)
GeruzoniAnsasu
2017-08-24 17:00:46 +08:00
➜ /tmp python3
Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> ia = ( x for x in range (10) )
>>> ib = ( x for x in range(10) )
>>> ii = zip(ia,ib)
>>> for a,b in ii:
... print(a,b)
...
0 0
1 1


python3 里的 zip 出来的结果本身就是一个迭代器了
wwqgtxx
2017-08-24 17:04:00 +08:00
@fanhaipeng0403 因为我忘记先调用 iter()了
wwqgtxx
2017-08-24 17:06:50 +08:00
@GeruzoniAnsasu 其实你这段代码的前两行并不用把 range 对象转换成 tuple 的,直接 zip()即可,这样能大幅度减少内存占用
wwqgtxx
2017-08-24 17:08:23 +08:00
>>> ii = zip(range(1,6),range(6,11),range(11,16))
>>> i = iter(ii)
>>> next(i)
(1, 6, 11)
>>> next(i)
(2, 7, 12)
>>> next(i)
(3, 8, 13)
>>> next(i)
(4, 9, 14)
>>> next(i)
(5, 10, 15)
>>> next(i)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
GeruzoniAnsasu
2017-08-24 17:09:27 +08:00
@wwqgtxx 不会转成 tuple,python3 里这个看似 tuple 的东西还是个迭代器。。
ferstar
2017-08-24 17:11:08 +08:00
换个思路, 像这样:
---
f1 = 'file1'
f2 = 'file2'
f3 = 'file3'


def do_sth(l1, l2, l3):
....pass


with open(f1, 'r') as fh1, open(f2, 'r') as fh2, open(f3, 'r') as fh3:
....while True:
........ f1_line = fh1.readline()
........f2_line = fh2.readline()
........f3_line = fh3.readline()
........do_sth(f1_line, f2_line, f3_line)
........if not f1_line or not f2_line or not f3_line:
............break
GeruzoniAnsasu
2017-08-24 17:11:17 +08:00
>>> def getwhat():
... a = ( x for x in range(1,10))
... b = ( x for x in range(15,24))
... c = zip(a,b)
... for v in c:
... for vv in v:
... yield vv
...
>>> [b for b in getwhat()]
[1, 15, 2, 16, 3, 17, 4, 18, 5, 19, 6, 20, 7, 21, 8, 22, 9, 23]
这样可以每次只返回一个
wwqgtxx
2017-08-24 17:12:29 +08:00
@GeruzoniAnsasu 好吧,还真的是,不过这样两层迭代器嵌套也没啥意思。。
>>> ( x for x in range (10) )
<generator object <genexpr> at 0x000001F09127FFC0>
wwqgtxx
2017-08-24 17:13:08 +08:00
@GeruzoniAnsasu 准确说应该是一个生成器而不是迭代器
ferstar
2017-08-24 17:15:27 +08:00
@ferstar readline()就是逐行读的, 下次调用读的是下一行, 所以可以完美解决你这个问题
mark06
2017-08-24 17:43:14 +08:00
#!usr/bin/env python2.7
from itertools import izip

a = (x for x in range(1, 6))
b = (x for x in range(6, 11))
c = (x for x in range(11, 16))

ii = izip(a, b, c)


def func():
for i, (x, y, z) in enumerate(ii):
yield x
yield y
yield z


for item in func():
print item


---

izip 返回的对象是个迭代器,你看看是否合适。
kkzxak47
2017-08-24 17:46:16 +08:00
itertools 是你的好朋友
https://docs.python.org/2/library/itertools.html

用里面叫 roundrobin 的函数
mark06
2017-08-24 17:51:19 +08:00
@mark06 代码贴丑了,抱歉。

PS:萌新求教,如何正确的贴代码并且不影响代码缩进问题的呀?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/385494

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX