395 votes

Comment diviser une liste en fonction d'une condition ?

Quelle est la meilleure façon, à la fois esthétiquement et du point de vue des performances, de diviser une liste d'éléments en plusieurs listes sur la base d'une condition ? 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 ?

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]

9 votes

J'ai atterri ici à la recherche d'un moyen d'avoir une condition dans l'instruction set builder, votre question a répondu à la mienne :)

8 votes

diviser est une description malheureuse de cette opération, puisqu'elle a déjà une signification spécifique en ce qui concerne les chaînes Python. Je pense que diviser est un terme 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 de str.split() , à 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]) frente a diviser les éléments d'une liste par catégorie.

0 votes

Discussion du même sujet sur python-list.

8voto

Biga Points 87
bad = []
good = [x for x in mylist if x in goodvals or bad.append(x)]

append renvoie None, donc cela fonctionne.

6voto

BJ Homer Points 29168

Personnellement, j'aime la version que vous avez citée, en supposant que vous disposiez déjà d'une liste de goodvals qui traîne. Si ce n'est pas le cas, quelque chose comme :

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

Bien sûr, cela revient à utiliser une liste de compréhension 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 que l'esthétique des listes de compréhension est très agréable. Bien sûr, si vous n'avez pas besoin de préserver l'ordre et que vous n'avez pas besoin de doublons, l'utilisation de la fonction intersection y difference Les méthodes sur les plateaux de tournage fonctionneraient également très bien.

5voto

michau Points 106

Si vous voulez le faire dans le 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 elle permet au moins de ne parcourir mylist qu'une seule fois.

5voto

FunkySayu Points 3002

Parfois, il semble que la compréhension des listes ne soit pas la meilleure chose à faire !

J'ai fait un petit test basé sur la réponse que les gens ont donné à 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 parti

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

L'utilisation de la 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%  --

4voto

Firegun Points 4018
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érifier cette

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