47 votes

Comment limiter la taille d'une compréhension?

J'ai un list et souhaitez construire (par l'intermédiaire d'une compréhension) une autre liste. Je voudrais que cette nouvelle liste pour être de taille limitée, par l'intermédiaire d'une condition

Le code suivant ne fonctionne pas:

a = [1, 2, 1, 2, 1, 2]
b = [i for i in a if i == 1 and len(b) < 3]

avec

Traceback (most recent call last):
  File "compr.py", line 2, in <module>
    b = [i for i in a if i == 1 and len(b) < 3]
  File "compr.py", line 2, in <listcomp>
    b = [i for i in a if i == 1 and len(b) < 3]
NameError: name 'b' is not defined

parce qu' b n'est pas défini encore au moment où la compréhension est construit.

Est-il un moyen de limiter la taille de la nouvelle liste au moment de la construction?

Note: j'ai pu briser la compréhension en for boucle avec le bon break lorsqu'un compteur est atteint, mais je voudrais savoir si il y a un mécanisme qui utilise une compréhension.

77voto

Martijn Pieters Points 271458

Vous pouvez utiliser une expression génératrice pour effectuer le filtrage, puis utiliser islice() pour limiter le nombre d'itérations:

 from itertools import islice

filtered = (i for i in a if i == 1)
b = list(islice(filtered, 3))
 

Cela garantit que vous ne faites pas plus de travail que nécessaire pour produire ces 3 éléments.

Notez qu’il n’est plus utile d’utiliser une liste de compréhension ici; une liste de compréhension ne peut pas être cassée, vous êtes enfermé dans une itération jusqu'à la fin.

6voto

MSeifert Points 6307

@Martijn Pieters est complètement à droite qu' itertools.islice est la meilleure façon de résoudre ce problème. Toutefois, si vous n'avez pas l'esprit d'une (externe) de la bibliothèque, vous pouvez utiliser iteration_utilities qui englobe un grand nombre de ces itertools et de leurs applications (et quelques autres). Il pourrait en faire un peu plus facile, au moins si vous aimez la programmation fonctionnelle:

>>> from iteration_utilities import Iterable

>>> Iterable([1, 2, 1, 2, 1, 2]).filter((1).__eq__)[:2].as_list()
[1, 1]

>>> (Iterable([1, 2, 1, 2, 1, 2])
...          .filter((1).__eq__)   # like "if item == 1"
...          [:2]                  # like "islice(iterable, 2)"
...          .as_list())           # like "list(iterable)"
[1, 1]

L' iteration_utilities.Iterable classe utilise des générateurs en interne, de sorte qu'il ne traitera autant d'éléments que nécessaire jusqu'à ce que vous appelez l'un de l' as_* (ou get_*) -méthodes.


Avertissement: je suis l'auteur de l' iteration_utilities bibliothèque.

4voto

Chris_Rands Points 15161

Vous pouvez utiliser itertools.count pour générer un compteur, itertools.takewhile d'arrêt de l'itération sur un générateur lorsque le compteur atteint le désiré entier (3 dans ce cas):

from itertools import count, takewhile
c = count()
b = list(takewhile(lambda x: next(c) < 3, (i for i in a if i == 1)))

Ou une idée similaire à la construction d'une construire de soulever StopIteration de résilier le générateur. Qui est le plus proche que vous obtiendrez à votre idée, à l'origine de la rupture de la compréhension de liste, mais je ne le recommanderais pas comme les meilleures pratiques:

c = count()
b = list(i if next(c) < 3 else next(iter([])) for i in a if i == 1)

Exemples:

>>> a = [1,2,1,4,1,1,1,1]

>>> c = count()
>>> list(takewhile(lambda x: next(c) < 3, (i for i in a if i == 1)))
[1, 1, 1]

>>> c = count()
>>> list(i if next(c) < 3 else next(iter([])) for i in a if i == 1)
[1, 1, 1]

0voto

jpp Points 83462

itertools.slice est la façon naturelle à l'extrait de n éléments à partir d'un générateur.

Mais vous pouvez également mettre en place vous-même à l'aide d'une fonction d'assistance. Tout comme l' itertools.slice pseudo-code, nous attrapons StopIteration afin de limiter le nombre d'éléments rapportés.

C'est plus souple car elle permet de préciser la logique si n est plus grand que le nombre d'éléments dans votre générateur.

def take_n(gen, n):
    for _ in range(n):
        try:
            yield next(gen)
        except StopIteration:
            break

g = (i**2 for i in range(5))
res = list(take_n(g, 20))

print(res)

[0, 1, 4, 9, 16]

-4voto

Dorianux Points 1

utiliser énumérer:

 b = [n for i,n in enumerate(a) if n==1 and i<3]
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X