Compreensão da lista: Retorno de dois (ou mais) itens para cada item

88

É possível retornar 2 (ou mais) itens para cada item em uma lista de compreensão?

O que eu quero (exemplo):

[f(x), g(x) for x in range(n)]

deveria retornar [f(0), g(0), f(1), g(1), ..., f(n-1), g(n-1)]

Então, algo para substituir este bloco de código:

result = list()
for x in range(n):
    result.add(f(x))
    result.add(g(x))
Hashmush
fonte
2
Por curiosidade, por que você quer fazer isso? Pode haver uma maneira melhor de atingir seu objetivo final sem tentar fazer dessa forma.
murgatroid99
3
Principalmente porque gosto de programação funcional. Quero mapear uma lista de coordenadas para uma tupla de coordenadas de tela para usar com a função pyglet.graphics.draw.
Hashmush

Respostas:

52
>>> from itertools import chain
>>> f = lambda x: x + 2
>>> g = lambda x: x ** 2
>>> list(chain.from_iterable((f(x), g(x)) for x in range(3)))
[2, 0, 3, 1, 4, 4]

Horários:

from timeit import timeit

f = lambda x: x + 2
g = lambda x: x ** 2

def fg(x):
    yield f(x)
    yield g(x)

print timeit(stmt='list(chain.from_iterable((f(x), g(x)) for x in range(3)))',
             setup='gc.enable(); from itertools import chain; f = lambda x: x + 2; g = lambda x: x ** 2')

print timeit(stmt='list(chain.from_iterable(fg(x) for x in range(3)))',
             setup='gc.enable(); from itertools import chain; from __main__ import fg; f = lambda x: x + 2; g = lambda x: x ** 2')

print timeit(stmt='[func(x) for x in range(3) for func in (f, g)]',
             setup='gc.enable(); f = lambda x: x + 2; g = lambda x: x ** 2')


print timeit(stmt='list(chain.from_iterable((f(x), g(x)) for x in xrange(10**6)))',
             setup='gc.enable(); from itertools import chain; f = lambda x: x + 2; g = lambda x: x ** 2',
             number=20)

print timeit(stmt='list(chain.from_iterable(fg(x) for x in xrange(10**6)))',
             setup='gc.enable(); from itertools import chain; from __main__ import fg; f = lambda x: x + 2; g = lambda x: x ** 2',
             number=20)

print timeit(stmt='[func(x) for x in xrange(10**6) for func in (f, g)]',
             setup='gc.enable(); f = lambda x: x + 2; g = lambda x: x ** 2',
             number=20)

2.69210777094

3.13900787874

1.62461071932

25.5944058287

29,2623711793

25.7211849286

Jamylak
fonte
4
Este código cria tuplas desnecessárias (f(x), g(x)). Poderia ser melhor escrito como: def fg(x): yield x + 2; yield x ** 2; list(chain.from_iterable(fg(x) for x in range(3))).
khachik
1
Você pode até mesmo generalizar com chain.from_iterable((func(x) for func in funcs) for x in range(n))). O que, aliás, eliminaria a reclamação de khachik. (Embora, em certo sentido, o meu e o dele sejam essencialmente os mesmos em termos de processo. Simplesmente definimos o gerador interno de maneira diferente.)
JAB
Isso é melhor do que minha sum(..., [])resposta porque não requer a recriação da lista em todos os + (portanto, tem desempenho O (N) em vez de desempenho O (N ^ 2)). Ainda vou usar sum(..., [])quando quero uma linha rápida ou estou com pressa, ou quando o número de termos sendo combinados é limitado (por exemplo, <= 10).
ninjagecko
@khachik Acho que isso seria mais rápido, mas vou cronometrar os dois métodos agora, embora as tuplas sejam geradas muito rápido em python.
Jamylak
3
Uma terceira resposta, que desapareceu, ficou assim: [y for x in range(n) for y in (f(x), g(x))]Mas provavelmente é mais lento. @jamylak Você também pode testar se quiser.
Hashmush
114

Compreensão de lista dupla:

[f(x) for x in range(5) for f in (f1,f2)]

Demo:

>>> f1 = lambda x: x
>>> f2 = lambda x: 10*x

>>> [f(x) for x in range(5) for f in (f1,f2)]
[0, 0, 1, 10, 2, 20, 3, 30, 4, 40]
ninjagecko
fonte
10
Isso é bom porque mostra que composições de lista dupla não são tão assustadoras: eles são simplesmente loops for aninhados escritos como loops for . for x in range(5): for f in (f1, f2): newlist.append(f(x)). Eu costumava achar um pouco confuso porque ficava tentando inverter a ordem.
DSM
1
Essa deve ser a resposta aceita, obrigado, incrível!
Wingjam
@DSM eu acho, será confuso para sempre.)
Winand
11
sum( ([f(x),g(x)] for x in range(n)), [] )

Isso é equivalente a [f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...

Você também pode pensar nisso como:

def flatten(list):
    ...

flatten( [f(x),g(x)] for x in ... )

nota: O jeito certo é usar itertools.chain.from_iterableou a compreensão de lista dupla. (Não requer a recriação da lista em todos os +, portanto, tem desempenho O (N) em vez de desempenho O (N ^ 2).) Ainda vou usar sum(..., [])quando quero uma linha rápida rápida ou estou com pressa , ou quando o número de termos sendo combinados é limitado (por exemplo, <= 10). É por isso que ainda o menciono aqui, com esta ressalva. Você também pode usar tuplas: ((f(x),g(x)) for ...), ()(ou pelo comentário de khachik, tendo um gerador fg (x) que resulta em duas tuplas).

ninjagecko
fonte
@ArashThr: it is doing[f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...
ninjagecko
Você pode explicar o que está fazendo exatamente?
Rsh
Nota: este tem tempo de execução O (N ^ 2), então pode ser lento em listas grandes.
Jamylak
1
@jamylak: sim, eu mencionei isso na sua resposta nos comentários também. =)
ninjagecko
Considero o abuso sum()dessa forma um antipadrão e não vejo nenhuma justificativa para usá-lo em quaisquer circunstâncias. O código em sua outra resposta é menos digitado, então mesmo a desculpa "quando eu quero uma linha rápida ou estou com pressa" não é suficiente.
Sven Marnach
2

Esta função lambda compacta duas listas em uma única:

zipped = lambda L1, L2: [L[i] 
                         for i in range(min(len(L1), len(L2))) 
                         for L in (L1, L2)]

Exemplo:

>>> f = [x for x in range(5)]
>>> g = [x*10 for x in range(5)]
>>> zipped(f, g)
[0, 0, 1, 10, 2, 20, 3, 30, 4, 40]
Daniel Reis
fonte
1

Sei que o OP está procurando uma solução de compreensão de lista, mas gostaria de oferecer uma alternativa de uso list.extend().

f = lambda x: x
g = lambda x: 10*x

result = []
extend = result.extend
for x in range(5):
    extend((f(x),g(x)))

o que é marginalmente mais rápido do que usar a compreensão de lista dupla.

nums = range(100000)

def double_comprehension():
    return [func(x) for x in nums for func in (f,g)]

def list_extend():
    result = []
    extend = result.extend
    for x in nums:
        extend((f(x),g(x)))
    return result

%timeit -n100 double_comprehension()
23.4 ms ± 67 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit -n100 list_extend()
20.5 ms ± 213 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Versão Python: 3.8.0

remykarem
fonte