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.

0voto

Chrisjan Points 51
images = [f for f in files if f[2].lower() in IMAGE_TYPES]
anims  = [f for f in files if f not in images]

C'est bien lorsque la condition est plus longue, comme dans votre exemple. Le lecteur n'a pas besoin de comprendre la condition négative et de savoir si elle englobe tous les autres cas.

0voto

Trylks Points 414

Vous pouvez faire de la programmation fonctionnelle paresseuse en Python, comme ceci :

partition = lambda l, c: map(
  lambda iii: (i for ii in iii for i in ii),
  zip(*(([], [e]) if c(e) else ([e], []) for e in l)))

La programmation fonctionnelle est élégante, mais pas en Python. Voir aussi cet exemple au cas où vous sauriez qu'il n'y a pas de None dans votre liste :

partition = lambda l, c: map(
  filter(lambda x: x is not None, l),
  zip(*((None, e) if c(e) else (e, None) for e in l)))

0voto

thehappycheese Points 41

Les réponses précédentes ne semblent pas satisfaire mes quatre tics obsessionnels compulsifs :

  1. Soyez aussi paresseux que possible,
  2. Évaluer l'Iterable original une seule fois
  3. Évaluer le prédicat une seule fois par élément
  4. Fournir des annotations de type agréables (pour python 3.7)

Ma solution n'est pas très jolie, et je ne pense pas pouvoir recommander son utilisation, mais la voici :

def iter_split_on_predicate(predicate: Callable[[T], bool], iterable: Iterable[T]) -> Tuple[Iterator[T], Iterator[T]]:
    deque_predicate_true = deque()
    deque_predicate_false = deque()

    # define a generator function to consume the input iterable
    # the Predicate is evaluated once per item, added to the appropriate deque, and the predicate result it yielded 
    def shared_generator(definitely_an_iterator):
        for item in definitely_an_iterator:
            print("Evaluate predicate.")
            if predicate(item):
                deque_predicate_true.appendleft(item)
                yield True
            else:
                deque_predicate_false.appendleft(item)
                yield False

    # consume input iterable only once,
    # converting to an iterator with the iter() function if necessary. Probably this conversion is unnecessary
    shared_gen = shared_generator(
        iterable if isinstance(iterable, collections.abc.Iterator) else iter(iterable)
    )

    # define a generator function for each predicate outcome and queue
    def iter_for(predicate_value, hold_queue):
        def consume_shared_generator_until_hold_queue_contains_something():
            if not hold_queue:
                try:
                    while next(shared_gen) != predicate_value:
                        pass
                except:
                    pass

        consume_shared_generator_until_hold_queue_contains_something()
        while hold_queue:
            print("Yield where predicate is "+str(predicate_value))
            yield hold_queue.pop()
            consume_shared_generator_until_hold_queue_contains_something()

    # return a tuple of two generators  
    return iter_for(predicate_value=True, hold_queue=deque_predicate_true), iter_for(predicate_value=False, hold_queue=deque_predicate_false)

En testant ce qui suit, nous obtenons la sortie ci-dessous à partir des instructions d'impression :

t,f = iter_split_on_predicate(lambda item:item>=10,[1,2,3,10,11,12,4,5,6,13,14,15])
print(list(zip(t,f)))
# Evaluate predicate.
# Evaluate predicate.
# Evaluate predicate.
# Evaluate predicate.
# Yield where predicate is True
# Yield where predicate is False
# Evaluate predicate.
# Yield where predicate is True
# Yield where predicate is False
# Evaluate predicate.
# Yield where predicate is True
# Yield where predicate is False
# Evaluate predicate.
# Evaluate predicate.
# Evaluate predicate.
# Evaluate predicate.
# Yield where predicate is True
# Yield where predicate is False
# Evaluate predicate.
# Yield where predicate is True
# Yield where predicate is False
# Evaluate predicate.
# Yield where predicate is True
# Yield where predicate is False
# [(10, 1), (11, 2), (12, 3), (13, 4), (14, 5), (15, 6)]

0voto

Ymareth Points 285

Une version basée sur un générateur, si vous pouvez supporter une ou deux inversions de la liste originale.

Une configuration...

random.seed(1234)
a = list(range(10))
random.shuffle(a)
a
[2, 8, 3, 5, 6, 4, 9, 0, 1, 7]

Et le partage...

(list((a.pop(j) for j, y in [(len(a)-i-1, x) for i,x in enumerate(a[::-1])] if y%2 == 0))[::-1], a)
([2, 8, 6, 4, 0], [3, 5, 9, 1, 7])
  1. Une autre liste de tuples de lieux et chaque élément est construit dans l'ordre inverse.
  2. Dans un générateur enveloppé, chaque élément est testé par rapport au prédicat (ici test pour pair) et s'il est vrai, l'élément est extrait à l'aide des emplacements calculés précédemment. Nous travaillons à rebours le long de la liste, de sorte que l'extraction d'éléments ne modifie pas les positions plus proches du début de la liste.
  3. Une liste enveloppante list() évalue le générateur et un renversement final [::-1] remet les éléments dans le bon ordre.
  4. La liste originale "a" ne contient plus que des éléments pour lesquels le prédicat est Faux.

0voto

ChaimG Points 2634

Clair et rapide

Cette liste de compréhension est simple à lire et rapide. Exactement ce que l'OP a demandé.

set_good_vals = set(good_vals)    # Speed boost.
good = [x for x in my_list if x in set_good_vals]
bad = [x for x in my_list if x not in set_good_vals]

Je préférerais une seule liste de compréhension plutôt que deux, mais contrairement à de nombreuses réponses affichées (dont certaines sont très ingénieuses), elle est lisible et claire. C'est également l'une des réponses les plus rapides de la page.

La seule réponse [légèrement] plus rapide est la suivante :

set_good_vals = set(good_vals)
good, bad = [], []
for item in my_list:
    _ = good.append(item) if item in set_good_vals else bad.append(item)

...et des variations de ce type. (Voir mon autre réponse). Mais je trouve la première méthode plus élégante et presque aussi rapide.

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