Python: fractionner une liste en fonction d'une condition?

273

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]
Parand
la source
5
atterri ici à la recherche d'un moyen d'avoir une condition dans la déclaration du constructeur, votre question a répondu à ma question :)
Anuvrat Parashar
5
split est une description malheureuse de cette opération, car elle a déjà une signification spécifique en ce qui concerne les chaînes Python. Je pense que diviser est un mot plus précis (ou du moins moins surchargé dans le contexte des itérables Python) pour décrire cette opération. J'ai atterri ici à la recherche d'un équivalent de liste str.split(), pour diviser la liste en une collection ordonnée de sous-listes consécutives. Par exemple split([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.
Ragoût
Discussion du même sujet sur python-list.
Xiong Chiamiov
IMAGE_TYPES devrait être un ensemble au lieu d'un tuple: IMAGE_TYPES = set('.jpg','.jpeg','.gif','.bmp','.png'). n (1) au lieu de n (o / 2), avec pratiquement aucune différence de lisibilité.
ChaimG

Réponses:

110
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?

Ce code est parfaitement lisible et extrêmement clair!

# 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]

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:

images, anims = [], []

for f in files:
    if f.lower() in IMAGE_TYPES:
        images.append(f)
    else:
        anims.append(f)

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 ..

if f[1] == 0:
    continue
dbr
la source
44
N'y a-t-il pas un moyen de comprendre la liste sans avoir à parcourir la liste deux fois?
balki
35
Le problème est que cela viole le principe DRY. Ce serait bien s'il y avait une meilleure façon de le faire.
Antimony
21
Une fois que l'appétit pour la programmation fonctionnelle (Haskell) ou le style fonctionnel (LINQ) est élevé, nous commençons à sentir Python pour son âge - [x for x in blah if ...]- verbeux, lambdamaladroit et limité ... On dirait de conduire la voiture la plus cool de 1995 aujourd'hui. Pas la même chose qu'à l'époque.
Tomasz Gandor
6
@TomaszGandor FTR, Haskell est plus ancien que Python (et a en fait influencé sa conception). Je pense que la syntaxe pour la compréhension de la liste et les lambdas a été délibérément gardée un peu du côté verbeux, peut-être pour décourager leur utilisation excessive. Ce qui est en effet un peu risqué ... autant que j'aime Haskell, je peux voir pourquoi beaucoup de gens trouvent Python généralement plus lisible.
leftaroundabout
4
la boucle simple for est la meilleure façon de le faire ... une boucle unique, très claire et lisible
Anentropic
217
good, bad = [], []
for x in mylist:
    (bad, good)[x in goodvals].append(x)
John La Rooy
la source
14
C'est incroyablement ingénieux! Il m'a fallu un certain temps pour comprendre ce qui se passait cependant. Je voudrais savoir si d'autres pensent que cela peut être considéré comme du code lisible ou non.
jgpaiva
171
good.append(x) if x in goodvals else bad.append(x)est plus lisible.
dansalmo
21
@dansalmo Surtout que vous pouvez en faire un monoligne avec le for-cycle, et si vous vouliez ajouter quelque chose de plus compliqué que x , vous pouvez en faire un appendseul:for x in mylist: (good if isgood(x) else bad).append(x)
yo '13
2
@MLister, dans ce cas, vous devriez probablement inclure la recherche d'attribut (bad.append, good.append)
John La Rooy
11
Une variation légèrement plus courte:(good if x in goodvals else bad).append(x)
Pi Delport
104

Voici l'approche par itérateur paresseux:

from itertools import tee

def split_on_condition(seq, condition):
    l1, l2 = tee((condition(item), item) for item in seq)
    return (i for p, i in l1 if p), (i for p, i in l2 if not p)

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:

from itertools import count, islice

def is_prime(n):
    return n > 1 and all(n % i for i in xrange(2, n))

primes, not_primes = split_on_condition(count(), is_prime)
print("First 10 primes", list(islice(primes, 10)))
print("First 10 non-primes", list(islice(not_primes, 10)))

Habituellement, bien que l'approche de retour de liste non paresseuse soit meilleure:

def split_on_condition(seq, condition):
    a, b = [], []
    for item in seq:
        (a if condition(item) else b).append(item)
    return a, b

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:

DROP_VALUE = lambda _:_
def split_by_key(seq, resultmapping, keyfunc, default=DROP_VALUE):
    """Split a sequence into lists based on a key function.

        seq - input sequence
        resultmapping - a dictionary that maps from target lists to keys that go to that list
        keyfunc - function to calculate the key of an input value
        default - the target where items that don't have a corresponding key go, by default they are dropped
    """
    result_lists = dict((key, []) for key in resultmapping)
    appenders = dict((key, result_lists[target].append) for target, keys in resultmapping.items() for key in keys)

    if default is not DROP_VALUE:
        result_lists.setdefault(default, [])
        default_action = result_lists[default].append
    else:
        default_action = DROP_VALUE

    for item in seq:
        appenders.get(keyfunc(item), default_action)(item)

    return result_lists

Usage:

def file_extension(f):
    return f[2].lower()

split_files = split_by_key(files, {'images': IMAGE_TYPES}, keyfunc=file_extension, default='anims')
print split_files['images']
print split_files['anims']
Fourmis Aasma
la source
Vous avez probablement raison de dire que cela viole le principe YAGNI. Il est basé sur l'hypothèse que le nombre de listes différentes dans lesquelles les éléments peuvent être partitionnés augmentera à l'avenir.
Ants Aasma
17
Cela peut être beaucoup de code, mais si cela [ x for x in my_list if ExpensiveOperation(x) ]prend beaucoup de temps à exécuter, vous ne voulez certainement pas le faire deux fois!
dash-tom-bang
1
+1 pour offrir plusieurs variantes, y compris basée sur un itérateur et une solution "in X" spécifique. Les PO «en bons termes» peuvent être petits, mais le remplacer par un très gros dictionnaire ou un prédicat coûteux peut être coûteux. De plus, cela réduit le besoin d'écrire la compréhension de la liste deux fois partout où cela est nécessaire, réduisant ainsi la probabilité d'introduction de fautes de frappe / erreur utilisateur. Belle solution. Merci!
cod3monk3y
3
Notez que teestocke 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.
John La Rooy
25

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:

def SplitIntoTwoLists(l, f):
  a = []
  b = []
  for i in l:
    if f(i):
      a.append(i)
    else:
      b.append(i)
 return (a,b)

De cette façon, vous ne traitez rien deux fois et vous ne répétez pas non plus de code.

winden
la source
Je suis d'accord. Je cherchais un moyen "élégant" (c'est-à-dire ici court et intégré / implicite) de le faire sans parcourir la liste deux fois, mais cela semble (sans profilage) être la voie à suivre. Bien sûr, cela n'aurait de toute façon d'importance que pour de grandes quantités de données.
Matthew Flaschen le
À mon humble avis, si vous connaissez un moyen de le faire avec moins d'utilisation du processeur (et donc moins de consommation d'énergie), il n'y a aucune raison de ne pas l'utiliser.
winden
2
@winden ... Portage de tout mon Python vers C.;)
Elliot Cameron
19

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:

  • maintenir l'ordre relatif des éléments (donc pas d'ensembles ni de dictionnaires)
  • évaluer la condition une seule fois pour chaque élément (donc ne pas utiliser ( i) filterougroupby )
  • permettre la consommation paresseuse de l'une ou l'autre séquence (si nous pouvons nous permettre de les précalculer, alors l'implémentation naïve est également susceptible d'être acceptable)

2. split bibliothèque

Ma partitionfonction (présentée ci-dessous) et d'autres fonctions similaires en ont fait une petite bibliothèque:

Il est installable normalement via PyPI:

pip install --user split

Pour diviser une base de liste à condition, utilisez la partitionfonction:

>>> from split import partition
>>> files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi') ]
>>> image_types = ('.jpg','.jpeg','.gif','.bmp','.png')
>>> images, other = partition(lambda f: f[-1] in image_types, files)
>>> list(images)
[('file1.jpg', 33L, '.jpg')]
>>> list(other)
[('file2.avi', 999L, '.avi')]

3. partition fonction expliquée

En 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):

from collections import deque

SplitSeq la classe s'occupe du ménage:

class SplitSeq:
    def __init__(self, condition, sequence):
        self.cond = condition
        self.goods = deque([])
        self.bads = deque([])
        self.seq = iter(sequence)

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:

    def getNext(self, getGood=True):
        if getGood:
            these, those, cond = self.goods, self.bads, self.cond
        else:
            these, those, cond = self.bads, self.goods, lambda x: not self.cond(x)
        if these:
            return these.popleft()
        else:
            while 1: # exit on StopIteration
                n = self.seq.next()
                if cond(n):
                    return n
                else:
                    those.append(n)

L'utilisateur final est censé utiliser la partitionfonction. Il prend une fonction de condition et une séquence (comme mapou filter) 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.

def partition(condition, sequence):
    cond = condition if condition else bool  # evaluate as bool if condition == None
    ss = SplitSeq(cond, sequence)
    def goods():
        while 1:
            yield ss.getNext(getGood=True)
    def bads():
        while 1:
            yield ss.getNext(getGood=False)
    return goods(), bads()

J'ai choisi la fonction de test pour être le premier argument pour faciliter l' application partielle dans l'avenir (similaire à la façon mapet filter avoir la fonction de test comme premier argument).

sastanin
la source
15

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é).

def categorize(func, seq):
    """Return mapping from categories to lists
    of categorized items.
    """
    d = defaultdict(list)
    for item in seq:
        d[func(item)].append(item)
    return d
Alan Isaac
la source
J'allais essayer de choisir les déclarations de Zen of Python qui s'appliquent ici, mais c'est trop pour un commentaire. =) Génial morceau de code.
jpmc26
13

Premier départ (pré-édition OP): utilisez des ensembles:

mylist = [1,2,3,4,5,6,7]
goodvals = [1,3,7,8,9]

myset = set(mylist)
goodset = set(goodvals)

print list(myset.intersection(goodset))  # [1, 3, 7]
print list(myset.difference(goodset))    # [2, 4, 5, 6]

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:

IMAGE_TYPES = set(['.jpg','.jpeg','.gif','.bmp','.png'])

et cela augmentera les performances. Sinon, ce que tu as me va bien.

RichieHindle
la source
4
pas la meilleure solution si les listes étaient dans un certain ordre avant de se séparer et que vous en avez besoin pour rester dans cet ordre.
Daniyar
8
Cela ne supprimerait-il pas les doublons?
mavnn
La création d'un ensemble est O (n log n). Itérer deux fois la liste est O (n). La solution définie peut être plus élégante (quand elle est correcte en premier lieu) mais est très certainement plus lente lorsque n augmente.
dash-tom-bang
1
@ dash-tom-bang L'itération de la liste est O (n * n). En effet, il peut être nécessaire de comparer chaque élément de la liste avec chaque élément de goodvals.
ChaimG
@ChaimG bon point, même si nous devons également prendre en compte le coût des opérations d'intersection et de différence (que je ne connais pas d'emblée, mais je suis à peu près sûr qu'elles sont également super linéaires).
dash-tom-bang
10

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.

def is_good(f):
    return f[2].lower() in IMAGE_TYPES

files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ('file3.gif', 123L, '.gif')]

for key, group in itertools.groupby(sorted(files, key=is_good), key=is_good):
    print key, list(group)

donne:

False [('file2.avi', 999L, '.avi')]
True [('file1.jpg', 33L, '.jpg'), ('file3.gif', 123L, '.gif')]

Semblable aux autres solutions, la fonction clé peut être définie pour se diviser en autant de groupes que vous le souhaitez.

Brian
la source
6
good.append(x) if x in goodvals else bad.append(x)

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:

good, bad = [], []
for x in my_list:
    good.append(x) if x in goodvals else bad.append(x)
John D
la source
5

Si vous voulez le faire en style FP:

good, bad = [ sum(x, []) for x in zip(*(([y], []) if y in goodvals else ([], [y])
                                        for y in mylist)) ]

Ce n'est pas la solution la plus lisible, mais au moins une itération dans mylist une seule fois.

michau
la source
1
Bien qu'il ne parcourt la liste qu'une seule fois, les performances ne sont pas bonnes à cause de la liste ajoutée. L'ajout à une liste est une opération potentiellement coûteuse (par rapport à deque.append par exemple). En fait, cette solution est extrêmement lente par rapport aux autres solutions ici (21,4 s sur 100 000 entiers aléatoires et tester leur valeur).
rlat
5

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:

good = filter(lambda x: is_good(x), mylist)
bad = filter(lambda x: not is_good(x), mylist)

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:

good = [x for x in mylist if is_good(x)]
bad  = [x for x in mylist if not is_good(x)]

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 intersectionet differencesur les ensembles fonctionnerait également bien.

BJ Homer
la source
Bien sûr, filter(lambda x: is_good(x), mylist)peut être réduit àfilter(is_good, mylist)
robru
l'ajout de l'appel de fonction supplémentaire double en fait (!) le temps d'exécution, par rapport aux compréhensions de liste, par rapport à ce que j'ai vu dans le profilage. il est difficile de battre une liste de compréhension, la plupart du temps.
Corley Brigman
4

Je pense qu'une généralisation du fractionnement d'un itérable basé sur N conditions est pratique

from collections import OrderedDict
def partition(iterable,*conditions):
    '''Returns a list with the elements that satisfy each of condition.
       Conditions are assumed to be exclusive'''
    d= OrderedDict((i,list())for i in range(len(conditions)))        
    for e in iterable:
        for i,condition in enumerate(conditions):
            if condition(e):
                d[i].append(e)
                break                    
    return d.values()

Par exemple:

ints,floats,other = partition([2, 3.14, 1, 1.69, [], None],
                              lambda x: isinstance(x, int), 
                              lambda x: isinstance(x, float),
                              lambda x: True)

print " ints: {}\n floats:{}\n other:{}".format(ints,floats,other)

 ints: [2, 1]
 floats:[3.14, 1.69]
 other:[[], None]

Si l'élément peut satisfaire plusieurs conditions, supprimez la rupture.

Gecko
la source
3
def partition(pred, iterable):
    'Use a predicate to partition entries into false entries and true entries'
    # partition(is_odd, range(10)) --> 0 2 4 6 8   and  1 3 5 7 9
    t1, t2 = tee(iterable)
    return filterfalse(pred, t1), filter(pred, t2)

Vérifiez ceci

Hanfei Sun
la source
3

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):

good_list = ('.jpg','.jpeg','.gif','.bmp','.png')

import random
import string
my_origin_list = []
for i in xrange(10000):
    fname = ''.join(random.choice(string.lowercase) for i in range(random.randrange(10)))
    if random.getrandbits(1):
        fext = random.choice(good_list)
    else:
        fext = "." + ''.join(random.choice(string.lowercase) for i in range(3))

    my_origin_list.append((fname + fext, random.randrange(1000), fext))

Et c'est reparti

# Parand
def f1():
    return [e for e in my_origin_list if e[2] in good_list], [e for e in my_origin_list if not e[2] in good_list]

# dbr
def f2():
    a, b = list(), list()
    for e in my_origin_list:
        if e[2] in good_list:
            a.append(e)
        else:
            b.append(e)
    return a, b

# John La Rooy
def f3():
    a, b = list(), list()
    for e in my_origin_list:
        (b, a)[e[2] in good_list].append(e)
    return a, b

# Ants Aasma
def f4():
    l1, l2 = tee((e[2] in good_list, e) for e in my_origin_list)
    return [i for p, i in l1 if p], [i for p, i in l2 if not p]

# My personal way to do
def f5():
    a, b = zip(*[(e, None) if e[2] in good_list else (None, e) for e in my_origin_list])
    return list(filter(None, a)), list(filter(None, b))

# BJ Homer
def f6():
    return filter(lambda e: e[2] in good_list, my_origin_list), filter(lambda e: not e[2] in good_list, my_origin_list)

En utilisant la fonction cmpthese , le meilleur résultat est la réponse dbr:

f1     204/s  --    -5%   -14%   -15%   -20%   -26%
f6     215/s     6%  --    -9%   -11%   -16%   -22%
f3     237/s    16%    10%  --    -2%    -7%   -14%
f4     240/s    18%    12%     2%  --    -6%   -13%
f5     255/s    25%    18%     8%     6%  --    -8%
f2     277/s    36%    29%    17%    15%     9%  --
FunkySayu
la source
Des fonctions plus rapides avec des repères mis à jour ici .
ChaimG
2

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:

from collections import deque

def split(iterable, function):
    dq_true = deque()
    dq_false = deque()

    # deque - the fastest way to consume an iterator and append items
    deque((
      (dq_true if function(item) else dq_false).append(item) for item in iterable
    ), maxlen=0)

    return dq_true, dq_false

Ensuite, vous pouvez utiliser la fonction de la manière suivante:

lower, higher = split([0,1,2,3,4,5,6,7,8,9], lambda x: x < 5)

selected, other = split([0,1,2,3,4,5,6,7,8,9], lambda x: x in {0,4,9})

Si l'objet ne vous convient pas deque, vous pouvez facilement le convertir en list, setcomme 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.

rlat
la source
2

Par exemple, fractionner la liste par pair et impair

arr = range(20)
even, odd = reduce(lambda res, next: res[next % 2].append(next) or res, arr, ([], []))

Ou en général:

def split(predicate, iterable):
    return reduce(lambda res, e: res[predicate(e)].append(e) or res, iterable, ([], []))

Avantages:

  • Manière la plus courte possible
  • Le prédicat ne s'applique qu'une seule fois pour chaque élément

Désavantages

  • Nécessite une connaissance du paradigme de programmation fonctionnelle
Pavel Ilchenko
la source
2

Inspiré par la grande réponse (mais laconique!) De @ gnibbler , nous pouvons appliquer cette approche pour mapper sur plusieurs partitions:

from collections import defaultdict

def splitter(l, mapper):
    """Split an iterable into multiple partitions generated by a callable mapper."""

    results = defaultdict(list)

    for x in l:
        results[mapper(x)] += [x]

    return results

Ensuite , splitterpeut ensuite être utilisé comme suit:

>>> l = [1, 2, 3, 4, 2, 3, 4, 5, 6, 4, 3, 2, 3]
>>> split = splitter(l, lambda x: x % 2 == 0)  # partition l into odds and evens
>>> split.items()
>>> [(False, [1, 3, 3, 5, 3, 3]), (True, [2, 4, 2, 4, 6, 4, 2])]

Cela fonctionne pour plus de deux partitions avec un mappage plus compliqué (et sur les itérateurs aussi):

>>> import math
>>> l = xrange(1, 23)
>>> split = splitter(l, lambda x: int(math.log10(x) * 5))
>>> split.items()
[(0, [1]),
 (1, [2]),
 (2, [3]),
 (3, [4, 5, 6]),
 (4, [7, 8, 9]),
 (5, [10, 11, 12, 13, 14, 15]),
 (6, [16, 17, 18, 19, 20, 21, 22])]

Ou en utilisant un dictionnaire pour cartographier:

>>> map = {'A': 1, 'X': 2, 'B': 3, 'Y': 1, 'C': 2, 'Z': 3}
>>> l = ['A', 'B', 'C', 'C', 'X', 'Y', 'Z', 'A', 'Z']
>>> split = splitter(l, map.get)
>>> split.items()
(1, ['A', 'Y', 'A']), (2, ['C', 'C', 'X']), (3, ['B', 'Z', 'Z'])]
Josh Bode
la source
... vient de remarquer que c'est essentiellement la même chose que @ alan-isaac a déjà répondu.
Josh Bode
2
bad = []
good = [x for x in mylist if x in goodvals or bad.append(x)]

append renvoie None, donc cela fonctionne.

Biga
la source
1

Pour la perfomance, essayez itertools.

Le module itertools standardise un ensemble de base d'outils rapides et efficaces en mémoire qui sont utiles seuls ou en combinaison. Ensemble, ils forment une «algèbre itérative» permettant de construire des outils spécialisés de manière succincte et efficace en Python pur.

Voir itertools.ifilter ou imap.

itertools.ifilter (prédicat, itérable)

Faire un itérateur qui filtre les éléments d'itérable en ne retournant que ceux pour lesquels le prédicat est vrai

gimel
la source
ifilter / imap (et les générateurs en général) sont assez lents ... en général, dans mon profilage, si vous prenez une compréhension de liste comme [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.
Corley Brigman
1

Parfois, vous n'aurez pas besoin de cette autre moitié de la liste. Par exemple:

import sys
from itertools import ifilter

trustedPeople = sys.argv[1].split(',')
newName = sys.argv[2]

myFriends = ifilter(lambda x: x.startswith('Shi'), trustedPeople)

print '%s is %smy friend.' % (newName, newName not in myFriends 'not ' or '')
Centre commercial Shikhar
la source
1

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.

good_list_set = set(good_list)  # 45% faster than a tuple.

good, bad = [], []
for item in my_origin_list:
    if item in good_list_set:
        good.append(item)
    else:
        bad.append(item)

Un peu plus court:

good_list_set = set(good_list)  # 45% faster than a tuple.

good, bad = [], []
for item in my_origin_list:
    out = good if item in good_list_set else bad
    out.append(item)

Résultats de référence:

filter_BJHomer                  80/s       --   -3265%   -5312%   -5900%   -6262%   -7273%   -7363%   -8051%   -8162%   -8244%
zip_Funky                       118/s    4848%       --   -3040%   -3913%   -4450%   -5951%   -6085%   -7106%   -7271%   -7393%
two_lst_tuple_JohnLaRoy         170/s   11332%    4367%       --   -1254%   -2026%   -4182%   -4375%   -5842%   -6079%   -6254%
if_else_DBR                     195/s   14392%    6428%    1434%       --    -882%   -3348%   -3568%   -5246%   -5516%   -5717%
two_lst_compr_Parand            213/s   16750%    8016%    2540%     967%       --   -2705%   -2946%   -4786%   -5083%   -5303%
if_else_1_line_DanSalmo         292/s   26668%   14696%    7189%    5033%    3707%       --    -331%   -2853%   -3260%   -3562%
tuple_if_else                   302/s   27923%   15542%    7778%    5548%    4177%     343%       --   -2609%   -3029%   -3341%
set_1_line                      409/s   41308%   24556%   14053%   11035%    9181%    3993%    3529%       --    -569%    -991%
set_shorter                     434/s   44401%   26640%   15503%   12303%   10337%    4836%    4345%     603%       --    -448%
set_if_else                     454/s   46952%   28358%   16699%   13349%   11290%    5532%    5018%    1100%     469%       --

Le code de référence complet pour Python 3.7 (modifié à partir de FunkySayu):

good_list = ['.jpg','.jpeg','.gif','.bmp','.png']

import random
import string
my_origin_list = []
for i in range(10000):
    fname = ''.join(random.choice(string.ascii_lowercase) for i in range(random.randrange(10)))
    if random.getrandbits(1):
        fext = random.choice(list(good_list))
    else:
        fext = "." + ''.join(random.choice(string.ascii_lowercase) for i in range(3))

    my_origin_list.append((fname + fext, random.randrange(1000), fext))

# Parand
def two_lst_compr_Parand(*_):
    return [e for e in my_origin_list if e[2] in good_list], [e for e in my_origin_list if not e[2] in good_list]

# dbr
def if_else_DBR(*_):
    a, b = list(), list()
    for e in my_origin_list:
        if e[2] in good_list:
            a.append(e)
        else:
            b.append(e)
    return a, b

# John La Rooy
def two_lst_tuple_JohnLaRoy(*_):
    a, b = list(), list()
    for e in my_origin_list:
        (b, a)[e[2] in good_list].append(e)
    return a, b

# # Ants Aasma
# def f4():
#     l1, l2 = tee((e[2] in good_list, e) for e in my_origin_list)
#     return [i for p, i in l1 if p], [i for p, i in l2 if not p]

# My personal way to do
def zip_Funky(*_):
    a, b = zip(*[(e, None) if e[2] in good_list else (None, e) for e in my_origin_list])
    return list(filter(None, a)), list(filter(None, b))

# BJ Homer
def filter_BJHomer(*_):
    return list(filter(lambda e: e[2] in good_list, my_origin_list)), list(filter(lambda e: not e[2] in good_list,                                                                             my_origin_list))

# ChaimG's answer; as a list.
def if_else_1_line_DanSalmo(*_):
    good, bad = [], []
    for e in my_origin_list:
        _ = good.append(e) if e[2] in good_list else bad.append(e)
    return good, bad

# ChaimG's answer; as a set.
def set_1_line(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        _ = good.append(e) if e[2] in good_list_set else bad.append(e)
    return good, bad

# ChaimG set and if else list.
def set_shorter(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        out = good if e[2] in good_list_set else bad
        out.append(e)
    return good, bad

# ChaimG's best answer; if else as a set.
def set_if_else(*_):
    good_list_set = set(good_list)
    good, bad = [], []
    for e in my_origin_list:
        if e[2] in good_list_set:
            good.append(e)
        else:
            bad.append(e)
    return good, bad

# ChaimG's best answer; if else as a set.
def tuple_if_else(*_):
    good_list_tuple = tuple(good_list)
    good, bad = [], []
    for e in my_origin_list:
        if e[2] in good_list_tuple:
            good.append(e)
        else:
            bad.append(e)
    return good, bad

def cmpthese(n=0, functions=None):
    results = {}
    for func_name in functions:
        args = ['%s(range(256))' % func_name, 'from __main__ import %s' % func_name]
        t = Timer(*args)
        results[func_name] = 1 / (t.timeit(number=n) / n) # passes/sec

    functions_sorted = sorted(functions, key=results.__getitem__)
    for f in functions_sorted:
        diff = []
        for func in functions_sorted:
            if func == f:
                diff.append("--")
            else:
                diff.append(f"{results[f]/results[func]*100 - 100:5.0%}")
        diffs = " ".join(f'{x:>8s}' for x in diff)

        print(f"{f:27s} \t{results[f]:,.0f}/s {diffs}")


if __name__=='__main__':
    from timeit import Timer
cmpthese(1000, 'two_lst_compr_Parand if_else_DBR two_lst_tuple_JohnLaRoy zip_Funky filter_BJHomer if_else_1_line_DanSalmo set_1_line set_if_else tuple_if_else set_shorter'.split(" "))
ChaimG
la source
0

Si vous insistez sur l'intelligence, vous pourriez prendre la solution de Winden et juste un peu d'intelligence fallacieuse:

def splay(l, f, d=None):
  d = d or {}
  for x in l: d.setdefault(f(x), []).append(x)
  return d
Anders Eurenius
la source
3
Le "d ou {}" est un peu dangereux. Si un dict vide est adopté, il ne sera pas modifié en place.
Brian
C'est vrai, mais il est retourné, alors ... En fait, c'est l'exemple parfait de la raison pour laquelle vous ne voulez pas ajouter plus d'intelligence à votre code. :-P
Anders Eurenius
0

Déjà pas mal de solutions ici, mais encore une autre façon de faire serait -

anims = []
images = [f for f in files if (lambda t: True if f[2].lower() in IMAGE_TYPES else anims.append(t) and False)(f)]

Itère sur la liste une seule fois et me semble un peu plus pythonique et donc lisible.

>>> files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi'), ('file1.bmp', 33L, '.bmp')]
>>> IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
>>> anims = []
>>> images = [f for f in files if (lambda t: True if f[2].lower() in IMAGE_TYPES else anims.append(t) and False)(f)]
>>> print '\n'.join([str(anims), str(images)])
[('file2.avi', 999L, '.avi')]
[('file1.jpg', 33L, '.jpg'), ('file1.bmp', 33L, '.bmp')]
>>>
Shreyas
la source
0

Je prendrais une approche en 2 passes, séparant l'évaluation du prédicat du filtrage de la liste:

def partition(pred, iterable):
    xs = list(zip(map(pred, iterable), iterable))
    return [x[1] for x in xs if x[0]], [x[1] for x in xs if not x[0]]

Ce qui est bien à ce sujet, en termes de performances (en plus d'évaluer predune seule fois sur chaque membre de iterable), 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.

Jim Witschey
la source
0

Solution

from itertools import tee

def unpack_args(fn):
    return lambda t: fn(*t)

def separate(fn, lx):
    return map(
        unpack_args(
            lambda i, ly: filter(
                lambda el: bool(i) == fn(el),
                ly)),
        enumerate(tee(lx, 2)))

tester

[even, odd] = separate(
    lambda x: bool(x % 2),
    [1, 2, 3, 4, 5])
print(list(even) == [2, 4])
print(list(odd) == [1, 3, 5])
doctorzeb8
la source
0

Si cela ne vous dérange pas d'utiliser une bibliothèque externe, je sais que deux implémentent nativement cette opération:

>>> files = [ ('file1.jpg', 33, '.jpg'), ('file2.avi', 999, '.avi')]
>>> IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
  • iteration_utilities.partition:

    >>> from iteration_utilities import partition
    >>> notimages, images = partition(files, lambda x: x[2].lower() in IMAGE_TYPES)
    >>> notimages
    [('file2.avi', 999, '.avi')]
    >>> images
    [('file1.jpg', 33, '.jpg')]
  • more_itertools.partition

    >>> from more_itertools import partition
    >>> notimages, images = partition(lambda x: x[2].lower() in IMAGE_TYPES, files)
    >>> list(notimages)  # returns a generator so you need to explicitly convert to list.
    [('file2.avi', 999, '.avi')]
    >>> list(images)
    [('file1.jpg', 33, '.jpg')]
MSeifert
la source
0

Je ne sais pas si c'est une bonne approche mais cela peut aussi être fait de cette façon

IMAGE_TYPES = ('.jpg','.jpeg','.gif','.bmp','.png')
files = [ ('file1.jpg', 33L, '.jpg'), ('file2.avi', 999L, '.avi')]
images, anims = reduce(lambda (i, a), f: (i + [f], a) if f[2] in IMAGE_TYPES else (i, a + [f]), files, ([], []))
Kiran
la source
0

Si la liste est composée de groupes et de séparateurs intermittents, vous pouvez utiliser:

def split(items, p):
    groups = [[]]
    for i in items:
        if p(i):
            groups.append([])
        groups[-1].append(i)
    return groups

Usage:

split(range(1,11), lambda x: x % 3 == 0)
# gives [[1, 2], [3, 4, 5], [6, 7, 8], [9, 10]]
David
la source
0
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims  = [f for f in files if f not in images]

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.

Chrisjan
la source
0

Encore une autre réponse, courte mais "diabolique" (pour les effets secondaires de la liste).

digits = list(range(10))
odd = [x.pop(i) for i, x in enumerate(digits) if x % 2]

>>> odd
[1, 3, 5, 7, 9]

>>> digits
[0, 2, 4, 6, 8]
Shay
la source