Compréhension de liste: retour de deux (ou plus) éléments pour chaque élément

88

Est-il possible de renvoyer 2 (ou plus) éléments pour chaque élément dans une compréhension de liste?

Ce que je veux (exemple):

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

devrait revenir [f(0), g(0), f(1), g(1), ..., f(n-1), g(n-1)]

Donc, quelque chose pour remplacer ce bloc de code:

result = list()
for x in range(n):
    result.add(f(x))
    result.add(g(x))
Hashmush
la source
2
Par curiosité, pourquoi voulez-vous faire cela? Il peut y avoir une meilleure façon d'atteindre votre objectif final sans essayer de le faire de cette façon.
murgatroid99
3
Principalement parce que j'aime la programmation fonctionnelle. Je veux mapper une liste de coordonnées à un tuple de coordonnées d'écran à utiliser avec la fonction pyglet.graphics.draw.
Hashmush

Réponses:

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]

Horaires:

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
la source
4
Ce code crée des tuples inutiles (f(x), g(x)). Peut - être mieux écrit: def fg(x): yield x + 2; yield x ** 2; list(chain.from_iterable(fg(x) for x in range(3))).
khachik
1
Vous pouvez même le généraliser avec chain.from_iterable((func(x) for func in funcs) for x in range(n))). Ce qui éliminerait d'ailleurs la plainte de Khachik. (Bien que dans un sens, le mien et le sien soient essentiellement les mêmes en termes de processus. Nous définissons simplement le générateur interne différemment.)
JAB
C'est mieux que ma sum(..., [])réponse car cela ne nécessite pas de recréer la liste tous les + (a donc des performances O (N) plutôt que des performances O (N ^ 2)). J'utiliserai toujours sum(..., [])quand je veux un one-liner rapide ou je suis pressé, ou quand le nombre de termes combinés est borné (par exemple <= 10).
ninjagecko
@khachik Je pense que ce serait plus rapide mais je vais chronométrer les deux méthodes maintenant, les tuples sont générés très rapidement en python.
jamylak
3
Une troisième réponse, qui a disparu, ressemblait à ceci: [y for x in range(n) for y in (f(x), g(x))]Mais c'est probablement plus lent. @jamylak Vous pouvez également tester cela si vous le souhaitez.
Hashmush
114

Compréhension de double liste:

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

Démo:

>>> 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
la source
10
C'est bien parce que cela montre que les compositions à double liste ne sont pas si effrayantes: elles sont simplement imbriquées pour des boucles écrites comme des boucles for . for x in range(5): for f in (f1, f2): newlist.append(f(x)). J'avais l'habitude de les trouver un peu déroutants parce que j'essayais toujours d'inverser l'ordre.
DSM
1
Cela devrait être la réponse acceptée, merci, incroyable!
Wingjam
@DSM je pense que ce sera déroutant pour toujours.)
Winand
11
sum( ([f(x),g(x)] for x in range(n)), [] )

Cela équivaut à [f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...

Vous pouvez également le considérer comme:

def flatten(list):
    ...

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

note: La bonne manière est d'utiliser itertools.chain.from_iterableou la double compréhension de liste. (Il ne nécessite pas de recréer la liste tous les +, donc a des performances O (N) plutôt que des performances O (N ^ 2).) J'utiliserai sum(..., [])quand même quand je veux un one-liner rapide ou je suis pressé , ou lorsque le nombre de termes combinés est borné (par exemple <= 10). C'est pourquoi je le mentionne encore ici, avec cette mise en garde. Vous pouvez également utiliser des tuples: ((f(x),g(x)) for ...), ()(ou par commentaire de khachik, avoir un générateur fg (x) qui donne un deux-tuple).

ninjagecko
la source
@ArashThr: il fait[f(1),g(1)] + [f(2),g(2)] + [f(3),g(3)] + ...
ninjagecko
Pouvez-vous expliquer ce qu'il fait exactement?
Rsh
Remarque: Cela a un runtime O (N ^ 2), donc il peut être lent sur de grandes listes.
jamylak
1
@jamylak: oui, j'ai également mentionné cela dans votre réponse dans les commentaires. =)
ninjagecko
Je considère abuser sum()de cette manière comme un anti-modèle, et je ne vois aucune justification pour l'utiliser en toutes circonstances. Le code de votre autre réponse est moins dactylographique, donc même l'excuse «quand je veux une ligne rapide ou que je suis pressé» ne suffit pas.
Sven Marnach
2

Cette fonction lambda zippe deux listes en une seule:

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

Exemple:

>>> 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
la source
1

Je sais qu'OP est à la recherche d'une solution de compréhension de liste, mais j'aimerais proposer une alternative d'utilisation 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)))

ce qui est légèrement plus rapide que l'utilisation de la compréhension de double liste.

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)

Version Python: 3.8.0

remykarem
la source