Eu tenho duas listas, a primeira das quais certamente conterá exatamente um item a mais do que a segunda . Gostaria de saber a maneira mais Pythônica de criar uma nova lista cujos valores de índice ímpar vêm da primeira lista e cujos valores de índice ímpar vêm da segunda lista.
# example inputs
list1 = ['f', 'o', 'o']
list2 = ['hello', 'world']
# desired output
['f', 'hello', 'o', 'world', 'o']
Isso funciona, mas não é bonito:
list3 = []
while True:
try:
list3.append(list1.pop(0))
list3.append(list2.pop(0))
except IndexError:
break
De que outra forma isso pode ser alcançado? Qual é a abordagem mais pitônica?
Respostas:
Esta é uma maneira de fazer isso fatiando:
>>> 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']
fonte
Há uma receita para isso na
itertools
documentação :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 a versão de python superior a 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))
fonte
zip_longest
.itertools
documentação porque.next()
não funciona mais.__next__
. Não está escrito na documentação, então propus uma edição para a resposta.Isso deve fazer o que você quiser:
>>> iters = [iter(list1), iter(list2)] >>> print list(it.next() for it in itertools.cycle(iters)) ['f', 'hello', 'o', 'world', 'o']
fonte
roundrobin
função é um pouco exagerada para essa situação.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]
Acho que essa é a maneira mais pitônica de fazer isso.
fonte
None
s que deveriam * estar na lista. Vou editar em uma versão ajustada para corrigir issoFalse
, ou mesmo coisas que só serão avaliadas comoFalse
pelaif
expressão-, como por exemplo a0
ou uma lista vazia. Isto pode ser (parcialmente) evitado pelo seguinte:[x for x in itertools.chain.from_iterable(itertools.zip_longest(list1, list2)) if x is not None]
. Claro, isso ainda não funcionará se as listas contiveremNone
elementos que precisam ser preservados. Nesse caso, você precisa mudar ofillvalue
argumento dezip_longest
, como Dubslow já sugeriu.None
o problema parece ter desaparecido, pelo menos desde o Python 3.7.6 (não sei para versões anteriores). Sealt_chain
for definido comodef alt_chain(*iters, fillvalue=None): return chain.from_iterable(zip_longest(*iters, fillvalue=fillvalue))
, entãolist(alt_chain([0, False, 1, set(), 3, 4], [0, None, 1, {}], fillvalue=99))
retorna corretamente[0, 0, False, None, 1, 1, set(), {}, 3, 99, 4, 99]
.Sem ferramentas e assumindo que l1 é 1 item maior do que l2:
>>> sum(zip(l1, l2+[0]), ())[:-1] ('f', 'hello', 'o', 'world', 'o')
Usando itertools e assumindo que as listas não contêm Nenhum:
>>> filter(None, sum(itertools.izip_longest(l1, l2), ())) ('f', 'hello', 'o', 'world', 'o')
fonte
[(l1[0], l2[0]), (l1[1], l2[1]), ...]
,.sum
concatena tuplas:(l1[0], l2[0]) + (l1[1], l2[1]) + ...
resultando em listas intercaladas. O resto da linha é apenas preenchimento de l1 com elemento extra para zip trabalhar e cortar até -1 para se livrar desse preenchimento.filter(None, ...
(poderia usar embool
vez disso, ouNone.__ne__
) remove valores falsos, incluindo 0, nenhum e strings vazias, então a segunda expressão não é estritamente equivalente à primeira.sum
isso? Qual é o papel do segundo argumento aí? Nas documentações, o segundo argumento éstart
.Sei que as perguntas são sobre duas listas, uma com um item a mais do que a outra, mas imaginei que colocaria isso para outras pessoas que possam encontrar essa pergunta.
Aqui está a solução de Duncan adaptada para trabalhar com duas listas de tamanhos diferentes.
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
Isso resulta em:
['f', 'hello', 'o', 'world', 'o', 'b', 'a', 'r']
fonte
Se ambas as listas tiverem comprimento igual, você pode fazer:
[x for y in zip(list1, list2) for x in y]
Como a primeira lista tem mais um elemento, você pode adicioná-lo post hoc:
[x for y in zip(list1, list2) for x in y] + [list1[-1]]
fonte
Aqui está um liner que faz isso:
list3 = [ item for pair in zip(list1, list2 + [0]) for item in pair][:-1]
fonte
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
fonte
def combine(list1, list2, lst=[]):
, sua primeira linha lida , daí meu comentário. No momento em que enviei esse comentário, entretanto, killown havia feito a mudança necessária.Este é baseado na contribuição de Carlos Valiente acima com uma opção para alternar grupos de vários itens e certificar-se de que todos os itens estão presentes na saída:
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)]
Isso entrelaçará as listas A e B por grupos de 3 valores cada:
['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]
fonte
Pode ser um pouco tarde para comprar mais uma linha de python. Isso funciona quando as duas listas têm tamanhos iguais ou desiguais. Uma coisa que não vale nada é que modificará a e b. Se for um problema, você precisará usar outras soluções.
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']
fonte
Minha vez:
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)]
fonte
Aqui está uma linha usando compreensões de lista, sem outras bibliotecas:
list3 = [sub[i] for i in range(len(list2)) for sub in [list1, list2]] + [list1[-1]]
Aqui está outra abordagem, se você permitir a alteração de sua lista1 inicial por efeito colateral:
[list1.insert((i+1)*2-1, list2[i]) for i in range(len(list2))]
fonte
from itertools import chain list(chain(*zip('abc', 'def'))) # Note: this only works for lists of equal length ['a', 'd', 'b', 'e', 'c', 'f']
fonte
Pára no mais curto:
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 o método next / __ next__ pode ser mais rápido.
fonte
Isso é desagradável, mas funciona independentemente do tamanho das listas:
list3 = [element for element in list(itertools.chain.from_iterable([val for val in itertools.izip_longest(list1, list2)])) if element != None]
fonte
Vários one-liners inspirados por respostas a outra pergunta :
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]
fonte
Que tal entorpecido? Também funciona com strings:
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])
fonte
Uma alternativa de forma funcional e imutável (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),[])
fonte
Eu faria o simples:
Ele virá com um iterador sem criar nenhuma necessidade de armazenamento adicional.
fonte
chain.from_iterable(izip(list1, list2), list1[len(list2):])
para o problema específico perguntado aqui ... a lista1 deve ser a mais longa.Estou muito velho para entender listas, então:
import operator list3 = reduce(operator.add, zip(list1, list2))
fonte