Comprensión de listas: devolver dos (o más) elementos para cada elemento

89

¿Es posible devolver 2 (o más) elementos para cada elemento en una lista de comprensión?

Lo que quiero (ejemplo):

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

debería volver [f(0), g(0), f(1), g(1), ..., f(n-1), g(n-1)]

Entonces, algo para reemplazar este bloque de código:

result = list()
for x in range(n):
    result.add(f(x))
    result.add(g(x))
Hachís
fuente
3
Por curiosidad, ¿por qué quieres hacer esto? Puede haber una mejor manera de lograr su objetivo final sin intentar hacerlo de esta manera.
murgatroid99
3
Principalmente porque me gusta la programación funcional. Quiero mapear una lista de coordenadas a una tupla de coordenadas de pantalla para usar con la función pyglet.graphics.draw.
Hashmush

Respuestas:

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]

Tiempos:

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
fuente
4
Este código crea tuplas innecesarias (f(x), g(x)). Podría ser mejor escrito como: def fg(x): yield x + 2; yield x ** 2; list(chain.from_iterable(fg(x) for x in range(3))).
Khachik
1
Incluso podrías generalizarlo con chain.from_iterable((func(x) for func in funcs) for x in range(n))). Lo que, por cierto, eliminaría la queja de Khachik. (Aunque en cierto sentido, el mío y el suyo son esencialmente lo mismo en términos de proceso. Simplemente definimos el generador interno de manera diferente.)
JAB
Esto es mejor que mi sum(..., [])respuesta porque no requiere recrear la lista en cada + (por lo tanto, tiene un rendimiento O (N) en lugar de un rendimiento O (N ^ 2)). Lo seguiré usando sum(..., [])cuando quiera un resumen rápido o tenga prisa, o cuando el número de términos que se combinan sea limitado (por ejemplo, <= 10).
ninjagecko
@khachik Creo que esto sería más rápido, pero cronometraré ambos métodos ahora, aunque las tuplas se generan muy rápido en Python.
Jamylak
3
Una tercera respuesta, que desapareció, se veía así: [y for x in range(n) for y in (f(x), g(x))]pero probablemente esto sea más lento. @jamylak También puedes probar esto si quieres.
Hashmush
118

Comprensión de doble lista:

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

Manifestación:

>>> 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
fuente
10
Esto es bueno porque muestra que las composiciones de lista doble no dan tanto miedo: simplemente están anidadas para bucles for escritos como bucles for . for x in range(5): for f in (f1, f2): newlist.append(f(x)). Solía ​​encontrarlos un poco confusos porque seguía intentando invertir el orden.
DSM
1
Esta debería ser la respuesta aceptada, ¡gracias, increíble!
Wingjam
@DSM creo que será confuso para siempre.)
Winand
11
sum( ([f(x),g(x)] for x in range(n)), [] )

Esto es equivalente a [f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...

También puede pensar en ello como:

def flatten(list):
    ...

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

nota: La forma correcta es utilizar itertools.chain.from_iterableo la comprensión de doble lista. (No requiere recrear la lista en cada +, por lo tanto, tiene un rendimiento O (N) en lugar de un rendimiento O (N ^ 2)). Lo seguiré usando sum(..., [])cuando quiera una frase rápida o tenga prisa. , o cuando el número de términos que se combinan está acotado (por ejemplo, <= 10). Por eso todavía lo menciono aquí, con esta salvedad. También puede usar tuplas: ((f(x),g(x)) for ...), ()(o según el comentario de Khachik, que tiene un generador fg (x) que produce una tupla de dos).

ninjagecko
fuente
@ArashThr: está haciendo[f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...
ninjagecko
¿Puede explicar qué está haciendo exactamente?
Rsh
Nota: Esto tiene tiempo de ejecución O (N ^ 2), por lo que podría ser lento en listas grandes.
Jamylak
1
@jamylak: sí, también mencioné esto en tu respuesta en los comentarios. =)
ninjagecko
Considero abusar sum()de esta manera como un antipatrón, y no veo ninguna justificación para usarlo en cualquier circunstancia. El código en su otra respuesta es menos tipeado, por lo que incluso la excusa "cuando quiero una frase rápida o tengo prisa" no es suficiente.
Sven Marnach
2

Esta función lambda comprime dos listas en una sola:

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

Ejemplo:

>>> 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
fuente
2

Sé que OP está buscando una solución de comprensión de listas, pero me gustaría ofrecer una alternativa usando 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)))

que es ligeramente más rápido que usar la comprensión de lista doble.

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)

Versión de Python: 3.8.0

remykarem
fuente
0

Una solución que usa reducir :

from functools import reduce

f    = lambda x: f"f({x})" ## Just for example
g    = lambda x: f"g({x})"
data = [1, 2, 3]

reduce(lambda acc, x: acc + [f(x), g(x)], data, [])
# => ['f(1)', 'g(1)', 'f(2)', 'g(2)', 'f(3)', 'g(3)']

Si bien no es una lista de comprensión, esta es una forma funcional de abordar el problema. La comprensión de una lista es esencialmente otra forma de mapanalizar los datos, pero en este caso, donde el mapeo no es uno a uno entre la entrada y la salida,reduce permite cierto margen de maniobra con respecto a cómo se puede generar la salida.

En general, cualquier forimplementación del formulario:

result = []
for n in some_data:
  result += some_operation()
  ## etc.

(Es decir, para bucles destinados a producir un efecto secundario en una lista o estructura de datos similar)

Se puede refactorizar en una map/reduce/filterimplementación declarativa .

0112
fuente