如何并行遍历两个列表?

我有两个可迭代对象,我想成对检查它们:

foo = [1, 2, 3]bar = [4, 5, 6]
for (f, b) in iterate_together(foo, bar):print("f:", f, " |  b:", b)

这应导致:

f: 1  |  b: 4f: 2  |  b: 5f: 3  |  b: 6

一个的方法是遍历索引:

for i in range(len(foo)):print("f:", foo[i], " |  b:", bar[i])

但对我来说,这似乎有点不切实际。有没有更好的方法来做到这一点?

1097954 次浏览

您需要zip函数。

for (f,b) in zip(foo, bar):print "f: ", f ,"; b: ", b

python3

for f, b in zip(foo, bar):print(f, b)

foobar中较短的一个停止时,zip停止。

python3中,zip返回元组的迭代器,例如Python2中的itertools.izip。获取列表对于元组,使用list(zip(foo, bar))。并压缩直到两个迭代器都被压缩筋疲力尽,你会使用itertools.zip_longest.

python2

python2中,zip返回元组列表。当foobar不是大质量时,这很好。如果它们都是大质量的,那么形成zip(foo,bar)是不必要的大质量临时变量,并应替换为itertools.izipitertools.izip_longest,返回一个迭代器而不是列表。

import itertoolsfor f,b in itertools.izip(foo,bar):print(f,b)for f,b in itertools.izip_longest(foo,bar):print(f,b)

foobar耗尽时,izip停止。当foobar都耗尽时,izip_longest停止。当较短的迭代器耗尽时,izip_longest会产生一个元组,其中None位于与该迭代器对应的位置。如果您愿意,您还可以在None之外设置不同的fillvalue。参见这里的完整的故事


另请注意,zip及其类似zip的bretime可以接受任意数量的可迭代对象作为参数。例如,

for num, cheese, color in zip([1,2,3], ['manchego', 'stilton', 'brie'],['red', 'blue', 'green']):print('{} {} {}'.format(num, color, cheese))

印刷品

1 red manchego2 blue stilton3 green brie

您应该使用“zip”函数。这是一个您自己的zip函数的示例

def custom_zip(seq1, seq2):it1 = iter(seq1)it2 = iter(seq2)while True:yield next(it1), next(it2)

以下是如何使用列表理解

a = (1, 2, 3)b = (4, 5, 6)[print('f:', i, '; b', j) for i, j in zip(a, b)]

它打印:

f: 1 ; b 4f: 2 ; b 5f: 3 ; b 6

您可以使用理解将第n个元素捆绑到元组或列表中,然后使用生成器函数将它们传递出去。

def iterate_multi(*lists):for i in range(min(map(len,lists))):yield tuple(l[i] for l in lists)
for l1, l2, l3 in iterate_multi([1,2,3],[4,5,6],[7,8,9]):print(str(l1)+","+str(l2)+","+str(l3))

基于@陈志立的答案,我比较了两个相同列表的迭代性能,当使用Python 3.6的zip()函数、Python的enumerate()函数、使用手动计数器(参见count()函数)、使用index-list,以及在两个列表之一(foobar)的元素可能被用来索引另一个列表的特殊场景中。他们在打印和创建新列表方面的性能分别使用timeit()函数进行了调查,其中使用的重复次数为1000次。下面给出了我为执行这些调查而创建的一个Python脚本。foobar列表的大小从10到1,000,000个元素不等。

结果:

  1. 用于打印目的:在分解+/-5%的精度容差后,所有考虑的方法的性能都与zip()函数大致相似。当列表大小小于100个元素时会发生异常。在这种情况下,index-list方法比zip()函数稍慢,而enumerate()函数快约9%。其他方法产生了与zip()函数相似的性能。

    打印循环1000次

  2. 创建列表:探索了两种类型的列表创建方法:使用(a)list.append()方法和(b)列表理解。在分解了+/-5%的精度容差后,对于这两种方法,zip()函数被发现比enumerate()函数执行得更快,比使用列表索引,比使用手动计数器。在这些比较中,zip()函数的性能提升可以快5%到60%。有趣的是,使用foo的元素来索引bar可以产生与zip()函数相同或更快的性能(5%到20%)。

    创建列表-1000reps

理解这些结果:

程序员必须确定每个有意义或有意义的操作的计算时间。

例如,对于打印目的,如果这个时间标准是1秒,即10**0秒,然后查看左侧1秒的图的y轴,并将其水平投影,直到它到达单项曲线,我们看到超过144个元素的列表大小将对程序员产生显著的计算成本和意义。也就是说,本研究中提到的用于较小列表大小的方法获得的任何性能对程序员来说都是无关紧要的。程序员将得出结论,迭代print语句的zip()函数的性能与其他方法相似。

结论

list创建过程中使用zip()函数并行遍历两个列表可以获得显著的性能。当并行遍历两个列表以打印出两个列表的元素时,zip()函数将产生与enumerate()函数类似的性能,例如使用手动计数器变量、使用index-list,以及在两个列表之一(foobar)的元素可用于索引另一个列表的特殊场景中。

用于调查列表创建的Python 3.6脚本。

import timeitimport matplotlib.pyplot as pltimport numpy as np

def test_zip( foo, bar ):store = []for f, b in zip(foo, bar):#print(f, b)store.append( (f, b) )
def test_enumerate( foo, bar ):store = []for n, f in enumerate( foo ):#print(f, bar[n])store.append( (f, bar[n]) )
def test_count( foo, bar ):store = []count = 0for f in foo:#print(f, bar[count])store.append( (f, bar[count]) )count += 1
def test_indices( foo, bar, indices ):store = []for i in indices:#print(foo[i], bar[i])store.append( (foo[i], bar[i]) )
def test_existing_list_indices( foo, bar ):store = []for f in foo:#print(f, bar[f])store.append( (f, bar[f]) )

list_sizes = [ 10, 100, 1000, 10000, 100000, 1000000 ]tz = []te = []tc = []ti = []tii= []
tcz = []tce = []tci = []tcii= []
for a in list_sizes:foo = [ i for i in range(a) ]bar = [ i for i in range(a) ]indices = [ i for i in range(a) ]reps = 1000
tz.append( timeit.timeit( 'test_zip( foo, bar )','from __main__ import test_zip, foo, bar',number=reps))te.append( timeit.timeit( 'test_enumerate( foo, bar )','from __main__ import test_enumerate, foo, bar',number=reps))tc.append( timeit.timeit( 'test_count( foo, bar )','from __main__ import test_count, foo, bar',number=reps))ti.append( timeit.timeit( 'test_indices( foo, bar, indices )','from __main__ import test_indices, foo, bar, indices',number=reps))tii.append( timeit.timeit( 'test_existing_list_indices( foo, bar )','from __main__ import test_existing_list_indices, foo, bar',number=reps))
tcz.append( timeit.timeit( '[(f, b) for f, b in zip(foo, bar)]','from __main__ import foo, bar',number=reps))tce.append( timeit.timeit( '[(f, bar[n]) for n, f in enumerate( foo )]','from __main__ import foo, bar',number=reps))tci.append( timeit.timeit( '[(foo[i], bar[i]) for i in indices ]','from __main__ import foo, bar, indices',number=reps))tcii.append( timeit.timeit( '[(f, bar[f]) for f in foo ]','from __main__ import foo, bar',number=reps))
print( f'te  = {te}' )print( f'ti  = {ti}' )print( f'tii = {tii}' )print( f'tc  = {tc}' )print( f'tz  = {tz}' )
print( f'tce  = {te}' )print( f'tci  = {ti}' )print( f'tcii = {tii}' )print( f'tcz  = {tz}' )
fig, ax = plt.subplots( 2, 2 )ax[0,0].plot( list_sizes, te, label='enumerate()', marker='.' )ax[0,0].plot( list_sizes, ti, label='index-list', marker='.' )ax[0,0].plot( list_sizes, tii, label='element of foo', marker='.' )ax[0,0].plot( list_sizes, tc, label='count()', marker='.' )ax[0,0].plot( list_sizes, tz, label='zip()', marker='.')ax[0,0].set_xscale('log')ax[0,0].set_yscale('log')ax[0,0].set_xlabel('List Size')ax[0,0].set_ylabel('Time (s)')ax[0,0].legend()ax[0,0].grid( b=True, which='major', axis='both')ax[0,0].grid( b=True, which='minor', axis='both')
ax[0,1].plot( list_sizes, np.array(te)/np.array(tz), label='enumerate()', marker='.' )ax[0,1].plot( list_sizes, np.array(ti)/np.array(tz), label='index-list', marker='.' )ax[0,1].plot( list_sizes, np.array(tii)/np.array(tz), label='element of foo', marker='.' )ax[0,1].plot( list_sizes, np.array(tc)/np.array(tz), label='count()', marker='.' )ax[0,1].set_xscale('log')ax[0,1].set_xlabel('List Size')ax[0,1].set_ylabel('Performances ( vs zip() function )')ax[0,1].legend()ax[0,1].grid( b=True, which='major', axis='both')ax[0,1].grid( b=True, which='minor', axis='both')
ax[1,0].plot( list_sizes, tce, label='list comprehension using enumerate()',  marker='.')ax[1,0].plot( list_sizes, tci, label='list comprehension using index-list()',  marker='.')ax[1,0].plot( list_sizes, tcii, label='list comprehension using element of foo',  marker='.')ax[1,0].plot( list_sizes, tcz, label='list comprehension using zip()',  marker='.')ax[1,0].set_xscale('log')ax[1,0].set_yscale('log')ax[1,0].set_xlabel('List Size')ax[1,0].set_ylabel('Time (s)')ax[1,0].legend()ax[1,0].grid( b=True, which='major', axis='both')ax[1,0].grid( b=True, which='minor', axis='both')
ax[1,1].plot( list_sizes, np.array(tce)/np.array(tcz), label='enumerate()', marker='.' )ax[1,1].plot( list_sizes, np.array(tci)/np.array(tcz), label='index-list', marker='.' )ax[1,1].plot( list_sizes, np.array(tcii)/np.array(tcz), label='element of foo', marker='.' )ax[1,1].set_xscale('log')ax[1,1].set_xlabel('List Size')ax[1,1].set_ylabel('Performances ( vs zip() function )')ax[1,1].legend()ax[1,1].grid( b=True, which='major', axis='both')ax[1,1].grid( b=True, which='minor', axis='both')
plt.show()

我们可以使用索引来迭代…

foo = ['a', 'b', 'c']bar = [10, 20, 30]for indx, itm in enumerate(foo):print (foo[indx], bar[indx])

如果您想在使用zip()一起迭代多个列表时保留索引,您可以将zip对象传递给enumerate()

for i, (f, b) in enumerate(zip(foo, bar)):# do something

例如,如果您想打印出两个列表中值不同的位置,您可以按照以下方式进行操作。

foo, bar = ['a', 'b', 'c'], ['a', 'a', 'c']
for i, (f, b) in enumerate(zip(foo, bar)):if f != b:print(f"items at index {i} are different")    
# items at index 1 are different

如果你的列表没有相同的长度,那么zip()会迭代直到最短的列表结束。如果你想迭代到最长的列表结束,请使用内置的itertools模块中的#1。它默认将缺失值填充None(但你可以使用fillvalue参数将其更改为任何你想要的值)。

from itertools import zip_longestfor f, b in zip_longest(foo, bar):# do something