Tengo dos listas, la primera de las cuales está garantizada para contener exactamente un artículo más que la segunda . Me gustaría saber la forma más Pythonic 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 Pythonic?
Respuestas:
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']
fuente
Hay una receta para esto en la
itertools
documentación :from itertools import cycle, islice def roundrobin(*iterables): "roundrobin('ABC', 'D', 'EF') --> A D E B F C" # Recipe credited to George Sakkis pending = len(iterables) nexts = cycle(iter(it).next for it in iterables) while pending: try: for next in nexts: yield next() except StopIteration: pending -= 1 nexts = cycle(islice(nexts, pending))
EDITAR:
Para la versión de Python mayor que 3:
from itertools import cycle, islice def roundrobin(*iterables): "roundrobin('ABC', 'D', 'EF') --> A D E B F C" # Recipe credited to George Sakkis pending = len(iterables) nexts = cycle(iter(it).__next__ for it in iterables) while pending: try: for next in nexts: yield next() except StopIteration: pending -= 1 nexts = cycle(islice(nexts, pending))
fuente
zip_longest
.itertools
documentación porque.next()
ya no funciona.__next__
. No está escrito en la documentación, así que propuse una edición a la respuesta.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']
fuente
roundrobin
función es excesiva para esta situación.list(itertools.chain(map(next, itertools.cycle(iters)), *iters))
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.
fuente
None
los mensajes de correo electrónico * que se supone que están en la lista.False
, o incluso cosas que solo serán evaluaron comoFalse
por elif
-expresión, como por ejemplo, una0
o una lista vacía. Esto puede ser (parcialmente) evitarse por la siguiente:[x for x in itertools.chain.from_iterable(itertools.zip_longest(list1, list2)) if x is not None]
. Por supuesto, esto aún no funcionará si las listas contienenNone
elementos que deben conservarse. En este caso, debe cambiar elfillvalue
argumento dezip_longest
, como Dubslow ya sugirió.None
El problema parece haber desaparecido, al menos desde Python 3.7.6 (no lo sé para versiones anteriores). Sialt_chain
se define comodef alt_chain(*iters, fillvalue=None): return chain.from_iterable(zip_longest(*iters, fillvalue=fillvalue))
,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]
.Sin itertools y asumiendo que l1 es 1 elemento más largo que l2:
>>> sum(zip(l1, l2+[0]), ())[:-1] ('f', 'hello', 'o', 'world', 'o')
Usando itertools y asumiendo que las listas no contienen None:
>>> filter(None, sum(itertools.izip_longest(l1, l2), ())) ('f', 'hello', 'o', 'world', 'o')
fuente
[(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 del diagrama unifilar es solo el relleno de l1 con un elemento adicional para que funcione la cremallera y corte hasta -1 para deshacerse de ese relleno.filter(None, ...
(podría usarbool
en su lugar, oNone.__ne__
) elimina los valores falsos, incluidos 0, None y cadenas vacías, por lo que la segunda expresión no es estrictamente equivalente a la primera.sum
eso? ¿Cuál es el papel del segundo argumento allí? En las documentaciones, el segundo argumento esstart
.Sé que las preguntas se refieren a dos listas en las que una tiene un elemento más que la otra, pero pensé que pondría esto para otras personas 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 produce:
['f', 'hello', 'o', 'world', 'o', 'b', 'a', 'r']
fuente
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]]
fuente
Aquí hay un trazador de líneas que lo hace:
list3 = [ item for pair in zip(list1, list2 + [0]) for item in pair][:-1]
fuente
def combine(list1, list2): lst = [] len1 = len(list1) len2 = len(list2) for index in range( max(len1, len2) ): if index+1 <= len1: lst += [list1[index]] if index+1 <= len2: lst += [list2[index]] return lst
fuente
def combine(list1, list2, lst=[]):
, se leyó la primera línea , de ahí mi comentario. Sin embargo, cuando envié ese comentario, Killown había realizado el cambio necesario.Este se basa en la contribución de Carlos Valiente anterior con una opción para alternar grupos de varios elementos y asegurarse de que todos los elementos estén presentes en la salida:
A=["a","b","c","d"] B=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16] def cyclemix(xs, ys, n=1): for p in range(0,int((len(ys)+len(xs))/n)): for g in range(0,min(len(ys),n)): yield ys[0] ys.append(ys.pop(0)) for g in range(0,min(len(xs),n)): yield xs[0] xs.append(xs.pop(0)) print [x for x in cyclemix(A, B, 3)]
Esto entrelazará las listas A y B por grupos de 3 valores cada uno:
['a', 'b', 'c', 1, 2, 3, 'd', 'a', 'b', 4, 5, 6, 'c', 'd', 'a', 7, 8, 9, 'b', 'c', 'd', 10, 11, 12, 'a', 'b', 'c', 13, 14, 15]
fuente
Puede que sea un poco tarde para comprar otra línea de Python. Esto funciona cuando las dos listas tienen un tamaño igual o desigual. Una cosa que no vale nada es que modificará ay b. Si es un problema, debe utilizar otras soluciones.
a = ['f', 'o', 'o'] b = ['hello', 'world'] sum([[a.pop(0), b.pop(0)] for i in range(min(len(a), len(b)))],[])+a+b ['f', 'hello', 'o', 'world', 'o']
fuente
Mi toma:
a = "hlowrd" b = "el ol" def func(xs, ys): ys = iter(ys) for x in xs: yield x yield ys.next() print [x for x in func(a, b)]
fuente
Aquí hay una línea que usa listas por comprensión, sin otras bibliotecas:
list3 = [sub[i] for i in range(len(list2)) for sub in [list1, list2]] + [list1[-1]]
Aquí hay otro enfoque, si permite la alteración de su list1 inicial por efecto secundario:
[list1.insert((i+1)*2-1, list2[i]) for i in range(len(list2))]
fuente
from itertools import chain list(chain(*zip('abc', 'def'))) # Note: this only works for lists of equal length ['a', 'd', 'b', 'e', 'c', 'f']
fuente
Se detiene en el más corto:
def interlace(*iters, next = next) -> collections.Iterable: """ interlace(i1, i2, ..., in) -> ( i1-0, i2-0, ..., in-0, i1-1, i2-1, ..., in-1, . . . i1-n, i2-n, ..., in-n, ) """ return map(next, cycle([iter(x) for x in iters]))
Claro, resolver el método next / __ next__ puede ser más rápido.
fuente
Esto es desagradable pero funciona sin importar el tamaño de las listas:
list3 = [element for element in list(itertools.chain.from_iterable([val for val in itertools.izip_longest(list1, list2)])) if element != None]
fuente
Varias frases ingeniosas inspiradas en las respuestas a otra pregunta :
import itertools list(itertools.chain.from_iterable(itertools.izip_longest(list1, list2, fillvalue=object)))[:-1] [i for l in itertools.izip_longest(list1, list2, fillvalue=object) for i in l if i is not object] [item for sublist in map(None, list1, list2) for item in sublist][:-1]
fuente
¿Qué tal numpy? También funciona con cadenas:
import numpy as np np.array([[a,b] for a,b in zip([1,2,3],[2,3,4,5,6])]).ravel()
Resultado:
array([1, 2, 2, 3, 3, 4])
fuente
Una alternativa de forma funcional e inmutable (Python 3):
from itertools import zip_longest from functools import reduce reduce(lambda lst, zipped: [*lst, *zipped] if zipped[1] != None else [*lst, zipped[0]], zip_longest(list1, list2),[])
fuente
Haría lo simple:
Aparecerá con un iterador sin crear ninguna necesidad de almacenamiento adicional.
fuente
chain.from_iterable(izip(list1, list2), list1[len(list2):])
el problema particular que se pregunta aquí ... se supone que list1 es el más largo.Soy demasiado mayor para estar deprimido con listas por comprensión, así que:
import operator list3 = reduce(operator.add, zip(list1, list2))
fuente