¿Cómo itero a través de dos listas en paralelo?

11 minutos de lectura

avatar de usuario
nathan fellman

Tengo dos iterables y quiero repasarlos en pares:

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

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

Eso debería resultar en:

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

Una La forma de hacerlo es iterar sobre los índices:

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

Pero eso me parece algo poco pitónico. Hay una mejor manera de hacerlo?

avatar de usuario
unutbu

Pitón 3

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

zip se detiene cuando el más corto de foo o bar se detiene

En Pitón 3, zip

devuelve un iterador de tuplas, como itertools.izip en Python2. Para obtener una lista de tuplas, utilice list(zip(foo, bar)). Y para comprimir hasta que se agoten ambos iteradores, usaría
itertools.zip_más largo.

Pitón 2

En Pitón 2, zip

devuelve una lista de tuplas. Esto está bien cuando foo y bar no son masivos. Si ambos son masivos entonces forman zip(foo,bar) es una variable temporal innecesariamente masiva, y debe ser reemplazada por itertools.izip o
itertools.izip_longestque devuelve un iterador en lugar de una lista.

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 se detiene cuando foo o bar está agotado.
izip_longest se detiene cuando ambos foo y bar están agotados. Cuando se agotan los iteradores más cortos, izip_longest produce una tupla con None en la posición correspondiente a ese iterador. También puede establecer un diferente fillvalue además None si lo desea. Ver aquí para el historia completa.


Tenga en cuenta también que zip y es zip-like brethen puede aceptar un número arbitrario de iterables como argumentos. Por ejemplo,

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

huellas dactilares

1 red manchego
2 blue stilton
3 green brie

  • @unutbu ¿Por qué preferiría el método de OP sobre el izip uno (aunque el izip/ zip se ve mucho más limpio)?

    – armar

    14/03/2016 a las 19:23

  • Es posible que desee mencionar Python 3 primero, ya que probablemente esté más preparado para el futuro. Además, vale la pena señalar que en Python 3, zip() tiene exactamente esa ventaja que solo itertools.izip() tenía en Python 2 y, por lo tanto, suele ser el camino a seguir.

    – daniel s.

    14/06/2016 a las 17:40

  • ¿Puedo pedirle que actualice su respuesta para indicar explícitamente que zip y zip-como funciones de itertools ¿Aceptar cualquier número de iterables y no solo 2? Esta pregunta es canónica ahora y su respuesta es la única que vale la pena actualizar.

    – bóveda

    11 de julio de 2016 a las 15:01

  • que pasa si ademas quiero el indice i? ¿Puedo envolver ese zip en enumerar?

    –Charlie Parker

    06/03/2018 a las 18:05

  • @CharlieParker: Sí, puedes, pero entonces usarías for i, (f, b) in enumerate(zip(foo, bar)).

    – unutbu

    06/03/2018 a las 19:20

Quiere que el zip función.

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

  • Antes de Python 3.0 querrías usar itertools.izip si tiene un gran número de elementos.

    – Georg Scholly

    2 de noviembre de 2009 a las 21:35

Deberías usar ‘Código Postal‘ función. Aquí hay un ejemplo de cómo puede verse su propia función zip

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

  • ¿No tiene esto exactamente el mismo resultado que zip(seq1, seq2)?

    – Niklas Mertsch

    6 de junio de 2018 a las 9:35

  • @NiklasMertsch sí, tiene exactamente el mismo resultado. Acabo de proporcionar un ejemplo de cómo se ve la función zip

    – Vlad Bezden

    6 jun 2018 a las 15:40

  • Esta es una reinvención bastante limitada de zip y la redacción es bastante engañosa. Si va a reinventar la rueda (no, es una función integrada, no una dependencia), al menos esta respuesta acepta un número variable de iterables y generalmente se comporta como cabría esperar zip a.

    – ggorlen

    7 de febrero de 2021 a las 4:27


avatar de usuario
Oso del sol

Sobre la base de la respuesta de @unutbu, comparé el rendimiento de iteración de dos listas idénticas al usar Python 3.6 zip() funciones, Python enumerate() función, utilizando un contador manual (ver count() función), utilizando una lista de índice, y durante un escenario especial donde los elementos de una de las dos listas (ya sea foo o bar) se puede utilizar para indexar la otra lista. Sus desempeños para imprimir y crear una nueva lista, respectivamente, fueron investigados usando el timeit() función donde el número de repeticiones utilizadas fue de 1000 veces. A continuación se muestra uno de los scripts de Python que había creado para realizar estas investigaciones. Los tamaños de los foo y bar las listas habían oscilado entre 10 y 1.000.000 de elementos.

Resultados:

  1. Para fines de impresión: Se observó que los rendimientos de todos los enfoques considerados eran aproximadamente similares a los zip() después de factorizar una tolerancia de precisión de +/-5%. Se produjo una excepción cuando el tamaño de la lista era inferior a 100 elementos. En tal escenario, el método de lista de índice fue ligeramente más lento que el zip() funcionar mientras el enumerate() la función fue ~9% más rápida. Los otros métodos arrojaron un rendimiento similar al zip() función.

    Bucle de impresión 1000 repeticiones

  2. Para crear listas: Se exploraron dos tipos de enfoques de creación de listas: usando el (a) list.append() método y (b) lista de comprensión. Después de factorizar una tolerancia de precisión de +/-5%, para ambos enfoques, el zip() Se encontró que la función funcionaba más rápido que la enumerate() función, que usar un índice de lista, que usar un contador manual. La ganancia de rendimiento por parte del zip() la función en estas comparaciones puede ser entre un 5 % y un 60 % más rápida. Curiosamente, usando el elemento de foo al Indice bar puede producir rendimientos equivalentes o más rápidos (5% a 20%) que el zip() función.

    Crear lista - 1000 repeticiones

Dar sentido a estos resultados:

Un programador tiene que determinar la cantidad de tiempo de cómputo por operación que sea significativa o que sea significativa.

Por ejemplo, para propósitos de impresión, si este criterio de tiempo es 1 segundo, es decir, 10**0 segundos, entonces mirando el eje y del gráfico que está a la izquierda en 1 segundo y proyectándolo horizontalmente hasta que alcance las curvas monomiales , vemos que los tamaños de listas que tienen más de 144 elementos incurrirán en un costo de cómputo significativo y significarán para el programador. Es decir, cualquier rendimiento obtenido por los enfoques mencionados en esta investigación para tamaños de lista más pequeños será insignificante para el programador. El programador concluirá que el desempeño del zip() La función para iterar declaraciones de impresión es similar a los otros enfoques.

Conclusión

Se puede obtener un rendimiento notable utilizando el zip() función para iterar a través de dos listas en paralelo durante list creación. Al iterar a través de dos listas en paralelo para imprimir los elementos de las dos listas, el zip() La función producirá un rendimiento similar al de la enumerate() función, en cuanto al uso de una variable de contador manual, en cuanto al uso de una lista de índice, y en cuanto a durante el escenario especial donde los elementos de una de las dos listas (ya sea foo o bar) se puede utilizar para indexar la otra lista.

El script de Python 3.6 que se usó para investigar la creación de listas.

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()

avatar de usuario
don f

Puede agrupar los n-ésimos elementos en una tupla o lista utilizando la comprensión y luego distribuirlos con una función generadora.

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))

avatar de usuario
Pedro Mortensen

He aquí cómo hacerlo con un lista de comprensión:

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

Imprime:

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

avatar de usuario
Pedro Mortensen

Podemos usar un índice para iterar…

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

  • Por que usar enumerate si en realidad no estás usando itm? O cambia a print(itm, bar[index]) o simplemente hacer un bucle como for indx in range(len(foo))

    – Tomerikoo

    8 de febrero a las 5:14


¿Ha sido útil esta solución?