2010-09-09 28 views
52

Tengo dos listas, , la primera de las cuales se garantiza que contiene exactamente un elemento más que el segundo. Me gustaría conocer la forma más pitonica de crear una nueva lista cuyos valores de índice par provengan de la primera lista y cuyos valores de índice impar provengan de la segunda lista.¿Manera pitónica de combinar dos listas de forma alternativa?

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

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

Esto funciona, pero no es bastante:

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

¿Cómo, si es posible lograrlo? ¿Cuál es el enfoque más Pythonic?

+1

posible duplicado de [Alternando entre iteradores en Python] (http://stackoverflow.com/questions/2017895/alternating-between-iterators-in-python) –

+0

¡No es un duplicado! La respuesta aceptada en el artículo vinculado anteriormente produce una lista de tuplas, no una sola lista fusionada. –

+0

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

Respuesta

66

Aquí hay una manera de hacerlo por corte:

>>> 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'] 
+2

Gracias, Duncan. No me di cuenta de que es posible especificar un paso al cortar. Lo que me gusta de este enfoque es cómo se lee de forma natural. 1. Haga una lista de la longitud correcta. 2. Rellené los índices pares con los contenidos de list1. 3. Complete los índices impares con los contenidos de list2. ¡El hecho de que las listas sean de diferentes longitudes no es un problema en este caso! – davidchambers

+1

creo que sólo funciona cuando len (lista1) - len (list2) es 0 o 1. – xan

+0

Si las listas son de longitudes apropiadas entonces funciona, si no, entonces la pregunta original no especifica qué respuesta se espera. Se puede modificar fácilmente para manejar la mayoría de las situaciones razonables: por ejemplo, si desea que se ignoren los elementos adicionales, simplemente corte la lista más larga antes de comenzar; si desea que los elementos adicionales estén intercalados con Ninguno, simplemente asegúrese de que el resultado se inicialice con más Ninguno; si desea añadir elementos adicionales al final, haga lo mismo que ignorarlos y añádalos. – Duncan

44

Hay una receta para esto en el itertools documentation:

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

¿Hay algo que las herramientas iterativas no puedan hacer? +1. –

+21

@Manoj volando. Necesita el módulo de antigravedad para ese – cobbal

+0

. Me resulta más complicado de lo que debería ser. Hay una mejor opción a continuación usando 'zip_longest'. – Dubslow

24

Esto debería hacer lo que quiera:

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

Me gustó mucho su respuesta inicial. Aunque no abordaba perfectamente la cuestión, era una forma elegante de fusionar dos listas de la misma duración. Sugiero retenerlo, junto con la advertencia de longitud, en su respuesta actual. –

+0

Esto es similar a la respuesta de David, pero es estrictamente entrelazado (se detendrá en lugar de continuar con listas posteriores) – cobbal

+0

@cobbal: ¿No era eso lo que quería? –

-2

Haría lo simple:

chain.from_iterable(izip(list1, list2)) 

Aparecerá un iterador sin crear ninguna necesidad de almacenamiento adicional.

+0

Esto no funciona. Da '['f', 'hello', 'o', 'world']'. –

+1

¡Es realmente simple, pero solo funciona con listas de la misma longitud! –

+0

Tiene razón, requiere contenedores de igual longitud. Hrm ... – wheaties

-2

Soy demasiado viejo para estar abajo con listas por comprensión, por lo que:

import operator 
list3 = reduce(operator.add, zip(list1, list2)) 
+1

Esto da ('f', 'hello', 'o', 'world') que es incorrecto. – davidchambers

3

Aquí hay un chiste que lo hace:

list3 = [ item for pair in zip(list1, list2 + [0]) for item in pair][:-1]

+1

Esto funciona correctamente, pero me parece poco elegante ya que está haciendo tanto para lograr algo tan simple. No estoy diciendo que este enfoque sea ineficiente, simplemente que no es particularmente fácil de leer. – davidchambers

0

Aquí hay un chiste usando listas por comprensión, w/o otras bibliotecas:

list3 = [sub[i] for i in range(len(list2)) for sub in [list1, list2]] + [list1[-1]] 

Aquí es otro enfoque, si se permite que una lteration de la lista1 inicial por efecto colateral:

[list1.insert((i+1)*2-1, list2[i]) for i in range(len(list2))] 
1

Mi opinión:

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

paradas 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, la resolución de la siguiente método next__/__ puede ser más rápido.

1
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 
+0

Tenga mucho cuidado al usar argumentos predeterminados mutables. Esto solo devolverá la respuesta correcta la primera vez que se invoca, ya que primero se reutilizará para cada llamada posterior. Esto sería mejor escrito como lst = None ... si lst es None: lst = [], aunque no veo una razón convincente para optar por este enfoque sobre otros que se enumeran aquí. – davidchambers

+0

el lst está dentro de la función ... – killown

+0

lst se define dentro de la función así que es una variable local. El posible problema es que list1 y list2 se reutilizarán cada vez que utilice la función, incluso si llama a la función con listas diferentes. Consulte http://docs.python.org/tutorial/controlflow.html#default-argument-values ​​ – blokeley

8

Sin itertools y l1 suponiendo es 1 artículo ya que L2:

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

Usando itertools y suponiendo que las listas no contienen Ninguno:

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

Excelentes frases ingeniosas, especialmente la primera ... sin necesidad de 'itertools', ¡este es un ganador para mí! – MestreLion

+0

Esta es mi respuesta favorita. Es muy conciso. – mbomb007

2

Éste se basa en La contribución de Carlos Valiente sobre con la opción de alternar grupos de elementos múltiples y asegurarse de que todos los artículos 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 de 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] 
7

Sé que las preguntas se relaciona con dos listas con uno que tenga un elemento más que el otro, pero pensé que lo haría pon esto para otros que puedan encontrar esta pregunta.

Aquí está Duncan's solution adaptado 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 

Este salidas:

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

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] 
15
import itertools 
print [x for x in itertools.chain.from_iterable(itertools.izip_longest(list1,list2)) if x] 

Creo que esta es la manera más Pythonic de hacer eso.

+3

¿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! –

+1

el nombre del método es zip_longest no izip_longest –

+3

¡Mucho mejor que la respuesta aceptada! – Vinay87

0

múltiples de una sola línea inspirados por answers to another question:

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] 
Cuestiones relacionadas