1146 votes

Comment itérer à travers deux listes en parallèle?

Je dispose de deux itérables et je veux les parcourir en pair :

foo = [1, 2, 3]
bar = [4, 5, 6]

for (f, b) in iterate_together(foo, bar):
    print("f:", f, " |  b:", b)

Cela devrait donner :

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

Une façon de le faire est de parcourir les indices :

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

Mais cela me semble quelque peu non-pythonique. Y a-t-il une meilleure façon de le faire ?


Tâches connexes :<br> <a href="https://stackoverflow.com/questions/2407398/">Comment fusionner des listes en une liste de tuples ?</a> - étant donné les listes <code>foo</code> et <code>bar</code> ci-dessus, créez la liste <code>[(1, 4), (2, 5), (3, 6)]</code>.<br> <a href="https://stackoverflow.com/questions/209840">Comment puis-je créer un dictionnaire (dict) à partir de listes distinctes de clés et de valeurs ?</a> - créez le dict <code>{1: 4, 2: 5, 3: 6}</code>.<br>* <a href="https://stackoverflow.com/questions/1747817">Créer un dictionnaire avec compréhension</a> - construire un <code>dict</code> en utilisant <code>zip</code> dans une compréhension de dict.

1789voto

unutbu Points 222216

Python 3

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

zip s'arrête quand le plus court de foo ou bar s'arrête.

En Python 3, zip renvoie un itérateur de tuples, comme itertools.izip en Python2. Pour obtenir une liste de tuples, utilisez list(zip(foo, bar)). Et pour zipper jusqu'à ce que les deux itérateurs soient épuisés, vous utiliseriez itertools.zip_longest.

Python 2

En Python 2, zip renvoie une liste de tuples. C'est bien lorsque foo et bar ne sont pas massifs. S'ils sont tous les deux massifs, alors former zip(foo, bar) est une variable temporaire inutilement massive, et devrait être remplacée par itertools.izip ou itertools.izip_longest, qui renvoient un itérateur au lieu d'une liste.

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

izip s'arrête lorsque foo ou bar est épuisé. izip_longest s'arrête lorsque à la fois foo et bar sont épuisés. Lorsque le(s) itérateur(s) le plus court est épuisé, izip_longest renvoie un tuple avec None à la position correspondante de cet itérateur. Vous pouvez également définir une fillvalue différente de None si vous le souhaitez. Voir ici pour l'histoire complète.


Notez également que zip et ses semblables à zip peuvent accepter un nombre arbitraire d'itérables en arguments. Par exemple,

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

imprime

1 red manchego
2 blue stilton
3 green brie

0 votes

@unutbu Pourquoi préférerais-je la méthode de l'OP à celle de izip (même si izip / zip semble beaucoup plus propre) ?

3 votes

Vous voudrez peut-être mentionner Python 3 en premier, car c'est probablement plus pérenne. De plus, il est bon de souligner que dans Python 3, zip() a exactement cet avantage que seul itertools.izip() avait dans Python 2 et donc c'est généralement la meilleure option.

5 votes

Puis-je vous demander de mettre à jour votre réponse pour indiquer explicitement que les fonctions zip et les fonctions similaires à zip de itertools acceptent n'importe quel nombre d'itérables et pas seulement 2 ? Cette question est maintenant canonique et votre réponse est la seule qui vaut la peine d'être mise à jour.

84voto

Karl Guertin Points 2206

Vous voulez la fonction zip.

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

13 votes

Avant Python 3.0, vous voudriez utiliser itertools.izip si vous avez un grand nombre d'éléments.

21voto

Vlad Bezden Points 5024

Vous devriez utiliser la fonction 'zip'. Voici un exemple de comment votre propre fonction zip peut ressembler

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

1 votes

N'a-t-il pas exactement le même résultat que zip(seq1, seq2)?

1 votes

@NiklasMertsch oui, cela donne exactement le même résultat. J'ai juste donné un exemple de la façon dont fonctionne la fonction zip.

1 votes

Il s'agit d'une réinvention assez limitée de zip et la formulation est plutôt trompeuse. Si vous devez réinventer la roue (ne le faites pas - c'est une fonction intégrée, pas une dépendance), au moins cette réponse accepte un nombre variable d'itérables et se comporte généralement comme vous vous attendez à ce que zip le fasse.

14voto

robince Points 6523

La fonction intégrée zip fait exactement ce que vous voulez. Si vous souhaitez la même chose sur des itérables au lieu de listes, vous pourriez regarder itertools.izip qui fait la même chose mais donne les résultats un par un.

14voto

Sun Bear Points 1676

Sur la base de la réponse de @unutbu J'ai comparé les performances d'itération de deux listes identiques à l'aide de l'algorithme de Python 3.6. zip() les fonctions de Python enumerate() en utilisant un compteur manuel (voir count() ), en utilisant une liste d'index, et lors d'un scénario spécial où les éléments d'une des deux listes (soit foo ou bar ) peut être utilisé pour indexer l'autre liste. Leurs performances pour l'impression et la création d'une nouvelle liste, respectivement, ont été étudiées à l'aide de la fonction timeit() où le nombre de répétitions utilisées était de 1000 fois. L'un des scripts Python que j'avais créé pour effectuer ces recherches est donné ci-dessous. Les tailles des foo et bar Les listes variaient de 10 à 1 000 000 d'éléments.

Résultats :

  1. A des fins d'impression : Les performances de toutes les approches considérées ont été observées comme étant approximativement similaires à celles de l'approche de la zip() après avoir pris en compte une tolérance de précision de +/-5%. Une exception s'est produite lorsque la taille de la liste était inférieure à 100 éléments. Dans ce cas, la méthode de la liste d'index était légèrement plus lente que la méthode de la liste d'index. zip() tandis que la fonction enumerate() était ~9% plus rapide. Les autres méthodes ont donné des performances similaires à celles de la fonction zip() fonction.

    Print loop 1000 reps

  2. Pour créer des listes : Deux types d'approches de création de listes ont été explorés : l'utilisation de la (a) list.append() et (b) compréhension de la liste . Après prise en compte d'une tolérance de précision de +/-5%, pour ces deux approches, la zip() s'est avérée plus rapide que la fonction enumerate() que l'utilisation d'un index de liste, que l'utilisation d'un compteur manuel. Le gain de performance par la zip() dans ces comparaisons peut être de 5 à 60 % plus rapide. Il est intéressant de noter que l'utilisation de l'élément foo à l'index bar peut donner des performances équivalentes ou plus rapides (5 % à 20 %) que la zip() fonction.

    Creating List - 1000reps

Donner un sens à ces résultats :

Un programmeur doit déterminer la quantité de temps de calcul par opération qui est significative ou qui a de l'importance.

Par exemple, à des fins d'impression, si ce critère de temps est de 1 seconde, c'est-à-dire 10**0 sec, alors en regardant l'axe des y du graphique qui est à gauche à 1 sec et en le projetant horizontalement jusqu'à ce qu'il atteigne les courbes monomiales, nous voyons que les listes dont la taille est supérieure à 144 éléments entraîneront un coût de calcul important et significatif pour le programmeur. En d'autres termes, toute performance gagnée par les approches mentionnées dans cette enquête pour les listes de plus petite taille sera insignifiante pour le programmeur. Le programmeur en conclura que les performances de l'approche zip() pour itérer les instructions d'impression est similaire aux autres approches.

Conclusion

Des performances notables peuvent être obtenues en utilisant le zip() pour itérer à travers deux listes en parallèle pendant list création. Lorsque l'on itère à travers deux listes en parallèle pour imprimer les éléments des deux listes, la fonction zip() donnera des performances similaires à celles de la fonction enumerate() à l'utilisation d'une variable de compteur manuelle, à l'utilisation d'une liste d'index, et au scénario spécial où les éléments d'une des deux listes (soit foo ou bar ) peut être utilisé pour indexer l'autre liste.

Le script de Python3.6 qui a été utilisé pour étudier la création de listes.

import timeit
import matplotlib.pyplot as plt
import 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 = 0
    for 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()

2 votes

Presque tout le temps est consacré à l'impression dans vos tests de print. L'impression est coûteuse. La construction de la liste a aussi un coût.

0 votes

@user2357112supportsMonica D'accord. Pour l'impression, la performance de l'itération est déterminée par les opérations d'E/S du système lente, donc elle est insensible à la performance de la fonction zip() ou des autres approches que j'ai considérées.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X