¿Una forma pitónica de combinar (entrelazar, entrelazar, entrelazar) dos listas de forma alterna?

9 minutos de lectura

avatar de usuario de davidchambers
davidchambers

tengo dos listas el primero de los cuales está garantizado que contiene exactamente un elemento más que el segundo. Me gustaría conocer la forma más pitónica de crear una nueva lista cuyos valores de índice par provienen de la primera lista y cuyos valores de índice impar provienen de la segunda lista.

# example inputs
list1 = ['f', 'o', 'o']
list2 = ['hello', 'world']

# desired output
['f', 'hello', 'o', 'world', 'o']

Esto funciona, pero no es bonito:

list3 = []
while True:
    try:
        list3.append(list1.pop(0))
        list3.append(list2.pop(0))
    except IndexError:
        break

¿De qué otra manera se puede lograr esto? ¿Cuál es el enfoque más pitónico?


Si necesita manejar listas de longitud no coincidente (por ejemplo, la segunda lista es más larga, o la primera tiene más de un elemento más que la segunda), algunas soluciones aquí funcionarán mientras que otras requerirán ajustes. Para obtener respuestas más específicas, consulte ¿Cómo intercalar dos listas de diferente longitud? dejar el exceso de elementos al final, o ¿Cómo intercalar elegantemente dos listas de longitud desigual en python? para tratar de intercalar los elementos de manera uniforme.

  • @Paul: Sí, la respuesta aceptada no brinda la solución completa. Lea los comentarios y las otras respuestas. La pregunta es básicamente la misma y las otras soluciones se pueden aplicar aquí.

    – Félix Kling

    9 de septiembre de 2010 a las 17:23


  • @Felix: respetuosamente no estoy de acuerdo. Es cierto, las preguntas están en el mismo vecindario pero en realidad no son duplicadas. Como prueba vaga, eche un vistazo a las posibles respuestas aquí y compárelas con la otra pregunta.

    -Paul Sasik

    9 de septiembre de 2010 a las 17:28

  • Echa un vistazo a estos: stackoverflow.com/questions/7529376/…

    – palabras para el sabio

    9 de agosto de 2016 a las 22:22

Aquí hay una forma de hacerlo cortando:

>>> list1 = ['f', 'o', 'o']
>>> list2 = ['hello', 'world']
>>> result = [None]*(len(list1)+len(list2))
>>> result[::2] = list1
>>> result[1::2] = list2
>>> result
['f', 'hello', 'o', 'world', 'o']

  • Gracias, Duncan. No me di cuenta de que es posible especificar un paso al cortar. Lo que me gusta de este enfoque es la naturalidad con la que se lee. 1. Haz una lista de la longitud correcta. 2. Rellenó los índices pares con el contenido de list1. 3. Rellene los índices impares con el contenido de list2. ¡El hecho de que las listas tengan diferentes longitudes no es un problema en este caso!

    – davidchambers

    9 de septiembre de 2010 a las 17:39

  • Creo que solo funciona cuando len(list1) – len(list2) es 0 o 1.

    – xan

    9 de septiembre de 2010 a las 19:01

  • Si las listas tienen la longitud adecuada, entonces funciona; de lo contrario, la pregunta original no especifica qué respuesta se espera. Se puede modificar fácilmente para manejar las situaciones más razonables: por ejemplo, si desea que se ignoren los elementos adicionales, simplemente elimine la lista más larga antes de comenzar; si desea que los elementos adicionales se intercalen con Ninguno, simplemente asegúrese de que el resultado se inicialice con más Ninguno; si desea agregar elementos adicionales al final, haga lo mismo que para ignorarlos y luego agréguelos.

    – Duncan

    9 de septiembre de 2010 a las 19:58

  • Yo tampoco estaba claro. El punto que estaba tratando de señalar es que la solución de Duncan, a diferencia de muchas de las enumeradas, no se complica por el hecho de que las listas tienen una longitud desigual. Claro, es aplicable solo en un rango limitado de situaciones, pero preferiría una solución realmente elegante que funcione en este caso a una solución menos elegante que funcione para dos listas cualesquiera.

    – davidchambers

    9 de septiembre de 2010 a las 23:48

  • Puedes usar (2*len(lista1)-1) en lugar de (len(lista1)+len(lista2)), también prefiero [0::2] en vez de [::2].

    – Señor Británico

    10 de septiembre de 2010 a las 0:38

Avatar de usuario de David Z
david z

Hay una receta para esto en el itertools documentación (nota: para Python 3):

from itertools import cycle, islice

def roundrobin(*iterables):
    "roundrobin('ABC', 'D', 'EF') --> A D E B F C"
    # Recipe credited to George Sakkis
    num_active = len(iterables)
    nexts = cycle(iter(it).__next__ for it in iterables)
    while num_active:
        try:
            for next in nexts:
                yield next()
        except StopIteration:
            # Remove the iterator we just exhausted from the cycle.
            num_active -= 1
            nexts = cycle(islice(nexts, num_active))

  • Encuentro esta manera más complicada de lo que debe ser. Hay una mejor opción a continuación usando zip_longest.

    – Dubslow

    16 de noviembre de 2017 a las 12:39

  • @Dubslow Para este caso particular, sí, esto probablemente sea excesivo (como mencioné en un comentario en otro lugar), a menos que ya tenga acceso a él. Sin embargo, podría tener algunas ventajas en otras situaciones. Esta receta ciertamente no fue diseñada para este problema, simplemente lo resuelve.

    – David Z.

    16 de noviembre de 2017 a las 19:17

  • Para tu información, deberías usar la receta en el itertools documentación porque .next() ya no funciona

    – Juan W.

    2 de junio de 2020 a las 15:21

  • @johnw. uno tiene que usar __next__. No está escrito en la documentación, así que propuse una edición de la respuesta.

    – Marine Galantin

    5 de agosto de 2020 a las 12:53


  • @Marine Preferiría que hubieras cambiado el ejemplo de código existente, pero puedo arreglarlo yo mismo. ¡Gracias por contribuir!

    – David Z.

    5 de agosto de 2020 a las 17:53

Avatar de usuario de Vamsi Nerella
vamsi nerella

import itertools
print [x for x in itertools.chain.from_iterable(itertools.izip_longest(list1,list2)) if x]

Creo que esta es la forma más pitónica de hacerlo.

  • ¿Por qué esta no es la respuesta aceptada? ¡Este es el más corto y más pitónico y funciona con diferentes longitudes de lista!

    –Jairo Vadillo

    19 de agosto de 2016 a las 8:36

  • el nombre del método es zip_longest no izip_longest

    –Jairo Vadillo

    19 de agosto de 2016 a las 8:37

  • El problema con esto es que el valor de relleno predeterminado de zip_longest podría sobrescribir Nones que *se supone que están en la lista. Editaré en una versión modificada para arreglar esto

    – Dubslow

    16 de noviembre de 2017 a las 12:27

  • Nota: Esto causará problemas si las listas contienen elementos con valor Falseo incluso cosas que solo serán evaluado como False por el if-expresión, como por ejemplo a 0 o una lista vacía. Esto se puede evitar (parcialmente) con lo siguiente: [x for x in itertools.chain.from_iterable(itertools.zip_longest(list1, list2)) if x is not None]. Por supuesto, esto seguirá sin funcionar si las listas contienen None elementos que necesitan ser preservados. En este caso, debe cambiar el fillvalue argumento de zip_longestcomo ya sugirió Dubslow.

    – der_herr_g

    14 mayo 2019 a las 16:35

  • None el problema parece haber desaparecido, al menos desde Python 3.7.6 (no sé para versiones anteriores). Si alt_chain Se define como def alt_chain(*iters, fillvalue=None): return chain.from_iterable(zip_longest(*iters, fillvalue=fillvalue))después list(alt_chain([0, False, 1, set(), 3, 4], [0, None, 1, {}], fillvalue=99)) devuelve correctamente [0, 0, False, None, 1, 1, set(), {}, 3, 99, 4, 99].

    – paime

    23 de julio de 2020 a las 9:49


Avatar de usuario de Mark Byers
marca byers

En Python 2, esto debería hacer lo que quieras:

>>> iters = [iter(list1), iter(list2)]
>>> print list(it.next() for it in itertools.cycle(iters))
['f', 'hello', 'o', 'world', 'o']

Avatar de usuario de Zart
Zart

Sin itertools y suponiendo que l1 es 1 elemento más largo que l2:

>>> sum(zip(l1, l2+[0]), ())[:-1]
('f', 'hello', 'o', 'world', 'o')

En python 2, usando itertools y asumiendo que las listas no contienen Ninguno:

>>> filter(None, sum(itertools.izip_longest(l1, l2), ()))
('f', 'hello', 'o', 'world', 'o')

  • Esta es mi respuesta favorita. Es tan conciso.

    – bombomb007

    18 mayo 2017 a las 16:25

  • @anishtain4 zip toma pares de elementos como tuplas de listas, [(l1[0], l2[0]), (l1[1], l2[1]), ...]. sum concatena tuplas juntas: (l1[0], l2[0]) + (l1[1], l2[1]) + ... resultando en listas intercaladas. El resto de la línea única es solo el relleno de l1 con un elemento adicional para que zip funcione y corte hasta -1 para deshacerse de ese relleno.

    – Zart

    14 de mayo de 2020 a las 5:08

  • izip_longest (zip_longest desde python 3) no necesita +[0] relleno, implícitamente llena Ninguno cuando las longitudes de las listas no coinciden, mientras que filter(None, ... (podría utilizar bool en cambio, o None.__ne__) elimina los valores falsos, incluidos 0, Ninguno y cadenas vacías, por lo que la segunda expresión no es estrictamente equivalente a la primera.

    – Zart

    14 de mayo de 2020 a las 5:14

  • la pregunta es como hiciste sum ¿Haz eso? ¿Cuál es el papel del segundo argumento allí? En las documentaciones, el segundo argumento es start.

    – anishtain4

    14 mayo 2020 a las 14:20

  • El valor predeterminado de inicio es 0, y no puede hacer 0+ (algo, tupla), por lo tanto, el inicio se cambia a una tupla vacía.

    – Zart

    14 mayo 2020 a las 16:15

Si ambas listas tienen la misma longitud, puede hacer:

[x for y in zip(list1, list2) for x in y]

Como la primera lista tiene un elemento más, puede agregarlo post hoc:

[x for y in zip(list1, list2) for x in y] + [list1[-1]]

  • Esta es mi respuesta favorita. Es tan conciso.

    – bombomb007

    18 mayo 2017 a las 16:25

  • @anishtain4 zip toma pares de elementos como tuplas de listas, [(l1[0], l2[0]), (l1[1], l2[1]), ...]. sum concatena tuplas juntas: (l1[0], l2[0]) + (l1[1], l2[1]) + ... resultando en listas intercaladas. El resto de la línea única es solo el relleno de l1 con un elemento adicional para que zip funcione y corte hasta -1 para deshacerse de ese relleno.

    – Zart

    14 de mayo de 2020 a las 5:08

  • izip_longest (zip_longest desde python 3) no necesita +[0] relleno, implícitamente llena Ninguno cuando las longitudes de las listas no coinciden, mientras que filter(None, ... (podría utilizar bool en cambio, o None.__ne__) elimina los valores falsos, incluidos 0, Ninguno y cadenas vacías, por lo que la segunda expresión no es estrictamente equivalente a la primera.

    – Zart

    14 de mayo de 2020 a las 5:14

  • la pregunta es como hiciste sum ¿Haz eso? ¿Cuál es el papel del segundo argumento allí? En las documentaciones, el segundo argumento es start.

    – anishtain4

    14 mayo 2020 a las 14:20

  • El valor predeterminado de inicio es 0, y no puede hacer 0+ (algo, tupla), por lo tanto, el inicio se cambia a una tupla vacía.

    – Zart

    14 mayo 2020 a las 16:15

Avatar de usuario de la comunidad
Comunidad

Sé que las preguntas se refieren a dos listas, una de las cuales tiene un elemento más que el otro, pero pensé que pondría esto para otros que puedan encontrar esta pregunta.

Aquí está la solución de Duncan adaptada para trabajar con dos listas de diferentes tamaños.

list1 = ['f', 'o', 'o', 'b', 'a', 'r']
list2 = ['hello', 'world']
num = min(len(list1), len(list2))
result = [None]*(num*2)
result[::2] = list1[:num]
result[1::2] = list2[:num]
result.extend(list1[num:])
result.extend(list2[num:])
result

Esto da como resultado:

['f', 'hello', 'o', 'world', 'o', 'b', 'a', 'r'] 

¿Ha sido útil esta solución?