Quelle est la meilleure façon, tant sur le plan esthétique que du point de vue des performances, de diviser une liste d'éléments en plusieurs listes en fonction d'un conditionnel? L'équivalent de:
good = [x for x in mylist if x in goodvals]
bad = [x for x in mylist if x not in goodvals]
existe-t-il une manière plus élégante de procéder?
Mise à jour: voici le cas d'utilisation réel, pour mieux expliquer ce que j'essaie de faire:
# files looks like: [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ... ]
IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims = [f for f in files if f[2].lower() not in IMAGE_TYPES]
str.split()
, pour diviser la liste en une collection ordonnée de sous-listes consécutives. Par exemplesplit([1,2,3,4,5,3,6], 3) -> ([1,2],[4,5],[6])
, par opposition à la division des éléments d'une liste par catégorie.IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png')
. n (1) au lieu de n (o / 2), avec pratiquement aucune différence de lisibilité.Réponses:
Ce code est parfaitement lisible et extrêmement clair!
Encore une fois, c'est bien!
Il peut y avoir de légères améliorations des performances à l'aide des ensembles, mais c'est une différence triviale, et je trouve la compréhension de la liste beaucoup plus facile à lire, et vous n'avez pas à vous soucier de l'ordre foiré, des doublons supprimés, etc.
En fait, je peux faire une autre étape "en arrière", et utiliser simplement une boucle for simple:
La compréhension d'une liste ou l'utilisation
set()
est correcte jusqu'à ce que vous ayez besoin d'ajouter une autre vérification ou un autre morceau de logique - disons que vous voulez supprimer tous les jpeg de 0 octet, vous ajoutez simplement quelque chose comme ..la source
[x for x in blah if ...]
- verbeux,lambda
maladroit et limité ... On dirait de conduire la voiture la plus cool de 1995 aujourd'hui. Pas la même chose qu'à l'époque.la source
good.append(x) if x in goodvals else bad.append(x)
est plus lisible.x
, vous pouvez en faire unappend
seul:for x in mylist: (good if isgood(x) else bad).append(x)
(bad.append, good.append)
(good if x in goodvals else bad).append(x)
Voici l'approche par itérateur paresseux:
Il évalue la condition une fois par article et renvoie deux générateurs, donnant d'abord des valeurs à partir de la séquence où la condition est vraie, l'autre où elle est fausse.
Parce qu'il est paresseux, vous pouvez l'utiliser sur n'importe quel itérateur, même infini:
Habituellement, bien que l'approche de retour de liste non paresseuse soit meilleure:
Modifier: Pour votre cas d'utilisation plus spécifique de fractionnement d'éléments en différentes listes par une touche, voici une fonction générique qui fait cela:
Usage:
la source
[ x for x in my_list if ExpensiveOperation(x) ]
prend beaucoup de temps à exécuter, vous ne voulez certainement pas le faire deux fois!tee
stocke toutes les valeurs entre les itérateurs qu'il retourne, donc il n'économisera pas vraiment la mémoire si vous bouclez sur un générateur entier puis sur l'autre.Le problème avec toutes les solutions proposées est qu'il va scanner et appliquer la fonction de filtrage deux fois. Je ferais une petite fonction simple comme celle-ci:
De cette façon, vous ne traitez rien deux fois et vous ne répétez pas non plus de code.
la source
Mon point de vue. Je propose un paresseux, en un seul passage,
partition
, qui préserve l'ordre relatif dans les sous-séquences de sortie.1. Exigences
Je suppose que les exigences sont les suivantes:
i
)filter
ougroupby
)2.
split
bibliothèqueMa
partition
fonction (présentée ci-dessous) et d'autres fonctions similaires en ont fait une petite bibliothèque:Il est installable normalement via PyPI:
Pour diviser une base de liste à condition, utilisez la
partition
fonction:3.
partition
fonction expliquéeEn interne, nous devons construire deux sous-séquences à la fois, donc la consommation d'une seule séquence de sortie forcera également l'autre à être calculée. Et nous devons garder l'état entre les demandes des utilisateurs (stocker les éléments traités mais pas encore demandés). Pour garder l'état, j'utilise deux files d'attente doubles (
deques
):SplitSeq
la classe s'occupe du ménage:La magie opère dans sa
.getNext()
méthode. C'est presque comme.next()
des itérateurs, mais permet de spécifier quel type d'élément nous voulons cette fois. Derrière la scène, il ne rejette pas les éléments rejetés, mais les place à la place dans l'une des deux files d'attente:L'utilisateur final est censé utiliser la
partition
fonction. Il prend une fonction de condition et une séquence (commemap
oufilter
) et retourne deux générateurs. Le premier générateur construit une sous-séquence d'éléments pour lesquels la condition est vérifiée, le second construit la sous-séquence complémentaire. Les itérateurs et les générateurs permettent le fractionnement paresseux de séquences même longues ou infinies.J'ai choisi la fonction de test pour être le premier argument pour faciliter l' application partielle dans l'avenir (similaire à la façon
map
etfilter
avoir la fonction de test comme premier argument).la source
J'aime fondamentalement l'approche d'Anders car elle est très générale. Voici une version qui place le catégoriseur en premier (pour correspondre à la syntaxe du filtre) et utilise un défaut par défaut (supposé importé).
la source
Premier départ (pré-édition OP): utilisez des ensembles:
C'est bon pour la lisibilité (IMHO) et les performances.
Deuxième coup (post-OP-edit):
Créez votre liste de bonnes extensions comme un ensemble:
et cela augmentera les performances. Sinon, ce que tu as me va bien.
la source
goodvals
.itertools.groupby fait presque ce que vous voulez, sauf qu'il nécessite que les éléments soient triés pour vous assurer d'obtenir une seule plage contiguë, vous devez donc d'abord trier par votre clé (sinon vous obtiendrez plusieurs groupes entrelacés pour chaque type). par exemple.
donne:
Semblable aux autres solutions, la fonction clé peut être définie pour se diviser en autant de groupes que vous le souhaitez.
la source
Cette réponse élégante et concise de @dansalmo est apparue enterrée dans les commentaires, donc je la rediffuse ici en tant que réponse afin qu'elle puisse obtenir l'importance qu'elle mérite, en particulier pour les nouveaux lecteurs.
Exemple complet:
la source
Si vous voulez le faire en style FP:
Ce n'est pas la solution la plus lisible, mais au moins une itération dans mylist une seule fois.
la source
Personnellement, j’aime la version que vous avez citée, en supposant que vous avez déjà une liste de
goodvals
traîner. Sinon, quelque chose comme:Bien sûr, c'est vraiment très similaire à l'utilisation d'une compréhension de liste comme vous l'avez fait à l'origine, mais avec une fonction au lieu d'une recherche:
En général, je trouve très esthétique l'esthétique des listes de compréhension. Bien sûr, si vous n'avez pas réellement besoin de préserver l'ordre et n'avez pas besoin de doublons, l'utilisation des méthodes
intersection
etdifference
sur les ensembles fonctionnerait également bien.la source
filter(lambda x: is_good(x), mylist)
peut être réduit àfilter(is_good, mylist)
Je pense qu'une généralisation du fractionnement d'un itérable basé sur N conditions est pratique
Par exemple:
Si l'élément peut satisfaire plusieurs conditions, supprimez la rupture.
la source
Vérifiez ceci
la source
Parfois, il semble que la compréhension de la liste n'est pas la meilleure chose à utiliser!
J'ai fait un petit test basé sur la réponse que les gens ont donnée à ce sujet, testé sur une liste générée aléatoirement. Voici la génération de la liste (il y a probablement une meilleure façon de faire, mais ce n'est pas le sujet):
Et c'est reparti
En utilisant la fonction cmpthese , le meilleur résultat est la réponse dbr:
la source
Encore une autre solution à ce problème. J'avais besoin d'une solution aussi rapide que possible. Cela signifie qu'une seule itération sur la liste et de préférence O (1) pour ajouter des données à l'une des listes résultantes. Ceci est très similaire à la solution fournie par la sastanine , sauf beaucoup plus court:
Ensuite, vous pouvez utiliser la fonction de la manière suivante:
Si l'objet ne vous convient pas
deque
, vous pouvez facilement le convertir enlist
,set
comme bon vous semble (par exemplelist(lower)
). La conversion est beaucoup plus rapide, que la construction des listes directement.Cette méthode conserve l'ordre des éléments, ainsi que tous les doublons.
la source
Par exemple, fractionner la liste par pair et impair
Ou en général:
Avantages:
Désavantages
la source
Inspiré par la grande réponse (mais laconique!) De @ gnibbler , nous pouvons appliquer cette approche pour mapper sur plusieurs partitions:
Ensuite ,
splitter
peut ensuite être utilisé comme suit:Cela fonctionne pour plus de deux partitions avec un mappage plus compliqué (et sur les itérateurs aussi):
Ou en utilisant un dictionnaire pour cartographier:
la source
append renvoie None, donc cela fonctionne.
la source
Pour la perfomance, essayez
itertools
.Voir itertools.ifilter ou imap.
la source
[x for x in a if x > 50000]
sur un simple tableau de 100000 entiers (via random.shuffle),filter(lambda x: x> 50000, a)
cela prendra 2x plus de temps,ifilter(lambda x: x> 50000, a); list(result)
prend environ 2,3 fois plus longtemps. Etrange mais vrai.Parfois, vous n'aurez pas besoin de cette autre moitié de la liste. Par exemple:
la source
C'est le moyen le plus rapide.
Il utilise
if else
, (comme la réponse de dbr) mais crée d'abord un ensemble. Un ensemble réduit le nombre d'opérations de O (m * n) à O (log m) + O (n), ce qui entraîne une augmentation de 45% + de la vitesse.Un peu plus court:
Résultats de référence:
Le code de référence complet pour Python 3.7 (modifié à partir de FunkySayu):
la source
Si vous insistez sur l'intelligence, vous pourriez prendre la solution de Winden et juste un peu d'intelligence fallacieuse:
la source
Déjà pas mal de solutions ici, mais encore une autre façon de faire serait -
Itère sur la liste une seule fois et me semble un peu plus pythonique et donc lisible.
la source
Je prendrais une approche en 2 passes, séparant l'évaluation du prédicat du filtrage de la liste:
Ce qui est bien à ce sujet, en termes de performances (en plus d'évaluer
pred
une seule fois sur chaque membre deiterable
), c'est qu'il déplace beaucoup de logique hors de l'interpréteur et dans un code d'itération et de mappage hautement optimisé. Cela peut accélérer l'itération sur de longs itérables, comme décrit dans cette réponse .En termes d'expressivité, il tire parti des expressions expressives telles que les compréhensions et la cartographie.
la source
Solution
tester
la source
Si cela ne vous dérange pas d'utiliser une bibliothèque externe, je sais que deux implémentent nativement cette opération:
iteration_utilities.partition
:more_itertools.partition
la source
Je ne sais pas si c'est une bonne approche mais cela peut aussi être fait de cette façon
la source
Si la liste est composée de groupes et de séparateurs intermittents, vous pouvez utiliser:
Usage:
la source
Bien quand la condition est plus longue, comme dans votre exemple. Le lecteur n'a pas à comprendre la condition négative et s'il capture tous les autres cas.
la source
Encore une autre réponse, courte mais "diabolique" (pour les effets secondaires de la liste).
la source