web-dev-qa-db-fra.com

équivalent en python de filter () obtenant deux listes de sortie (c'est-à-dire la partition d'une liste)

Disons que j'ai une liste et une fonction de filtrage. En utilisant quelque chose comme

>>> filter(lambda x: x > 10, [1,4,12,7,42])
[12, 42]

Je peux obtenir les éléments correspondant au critère. Existe-t-il une fonction que je pourrais utiliser pour générer deux listes, l’un des éléments correspondants, l’un des éléments restants? Je pourrais appeler la fonction filter() deux fois, mais c'est un peu moche :)

Edit: l'ordre des éléments doit être conservé, et je peux avoir des éléments identiques plusieurs fois.

49
F'x

Essaye ça:

def partition(pred, iterable):
    trues = []
    falses = []
    for item in iterable:
        if pred(item):
            trues.append(item)
        else:
            falses.append(item)
    return trues, falses

Usage:

>>> trues, falses = partition(lambda x: x > 10, [1,4,12,7,42])
>>> trues
[12, 42]
>>> falses
[1, 4, 7]

Il existe également une suggestion de mise en œuvre dans itertools recettes :

from itertools import filterfalse, tee

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)

La recette provient de la documentation Python 3.x. En Python 2.x, filterfalse s'appelle ifilterfalse

44
Mark Byers
>>> def partition(l, p):
...     return reduce(lambda x, y: (x[0]+[y], x[1]) if p(y) else (x[0], x[1]+[y]), l,  ([], []))
... 
>>> partition([1, 2, 3, 4, 5], lambda x: x < 3)
([1, 2], [3, 4, 5])

et une version un peu plus laide mais plus rapide du code ci-dessus:

def partition(l, p):
    return reduce(lambda x, y: x[0].append(y) or x if p(y) else x[1].append(y) or x, l,  ([], []))

C'est la deuxième édition, mais je pense que c'est important:

 def partition(l, p):
     return reduce(lambda x, y: x[not p(y)].append(y) or x, l, ([], []))

Le deuxième et le troisième sont aussi rapides que le supérieur supérieur mais sont moins codés.

21
Mariy

Je pense que groupby pourrait être plus pertinent ici:

http://docs.python.org/library/itertools.html#itertools.groupby

Par exemple, diviser une liste en nombres pairs et impairs (ou peut être un nombre arbitraire de groupes):

>>> l=range(6)
>>> key=lambda x: x % 2 == 0
>>> from itertools import groupby
>>> {k:list(g) for k,g in groupby(sorted(l,key=key),key=key)}
    {False: [1, 3, 5], True: [0, 2, 4]}
6
Praveen Srinivasan

Si vous n'avez pas d'élément en double dans votre liste, vous pouvez certainement utiliser set:

>>> a = [1,4,12,7,42]
>>> b = filter(lambda x: x > 10, [1,4,12,7,42])
>>> no_b = set(a) - set(b)
set([1, 4, 7])

ou vous pouvez faire par une liste compréhensible:

>>> no_b = [i for i in a if i not in b]

NB: ce n'est pas une fonction, mais en connaissant le premier résultat fitler (), vous pouvez en déduire l'élément qui n'a pas beaucoup de critère de filtre. 

3
mouad

TL; DR

Le accepté, vote le plus voté [1] de Mark Byers

def partition(pred, iterable):
    trues = []
    falses = []
    for item in iterable:
        if pred(item):
            trues.append(item)
        else:
            falses.append(item)
    return trues, falses

est le plus simple et le le plus rapide.

Benchmarking des différentes approches

Les différentes approches suggérées peuvent être classées En trois catégories,

  1. manipulation de liste simple via lis.append, renvoyant un 2 listes de listes,
  2. lis.append à médiation par une approche fonctionnelle, renvoyant un 2 listes de listes,
  3. en utilisant la recette canonique donnée dans la documentation itertools fine , en renvoyant un 2-Tuple de générateurs.

Voici une implémentation à la vanille des trois techniques, d'abord L'approche fonctionnelle, puis itertools et finalement deux implémentations Différentes de manipulation de liste directe, l'alternative étant Utilisant False: zéro , True est un tour.

Notez que ceci est Python3 - par conséquent, reduce provient de functools - Et OP demande un tuple comme (positives, negatives) mais mes Implémentations renvoient toutes (negatives, positives)

$ ipython
Python 3.6.2 |Continuum Analytics, Inc.| (default, Jul 20 2017, 13:51:32) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import functools
   ...: 
   ...: def partition_fu(p, l, r=functools.reduce):
   ...:     return r(lambda x, y: x[p(y)].append(y) or x, l, ([], []))
   ...: 

In [2]: import itertools
   ...: 
   ...: def partition_it(pred, iterable,
   ...:               filterfalse=itertools.filterfalse,
   ...:               tee=itertools.tee):
   ...:     t1, t2 = tee(iterable)
   ...:     return filterfalse(pred, t1), filter(pred, t2)
   ...: 

In [3]: def partition_li(p, l):
   ...:     a, b = [], []
   ...:     for n in l:
   ...:         if p(n):
   ...:             b.append(n)
   ...:         else:
   ...:             a.append(n)
   ...:     return a, b
   ...: 

In [4]: def partition_li_alt(p, l):
   ...:     x = [], []
   ...:     for n in l: x[p(n)].append(n)
   ...:     return x
   ...: 

Nous avons besoin d’un prédicat à appliquer à nos listes et listes (là encore, parlant vaguement ) Sur lesquelles opérer.

In [5]: p = lambda n:n%2

In [6]: five, ten = range(50000), range(100000)

Pour résoudre le problème lié au test de l'approche itertools, il a été signalé par Par joeln le Le 31 oct. 13 à 6:17.

Absurdité. Vous avez calculé le temps nécessaire pour construire les générateurs Dans filterfalse et filter, mais vous n'avez pas itéré Via l'entrée ou appelé pred une fois! L’avantage de la recette itertools est qu’elle ne matérialise aucune liste, ni ne regarde Plus loin que nécessaire dans la saisie. Il appelle pred deux fois plus souvent que Et prend presque deux fois plus de temps que Byers et al.

J'ai pensé à une boucle vide qui instancie simplement tous les couples D'éléments dans les deux itérables renvoyés par les différentes fonctions de partition .

Premièrement, nous utilisons deux listes fixes pour avoir une idée de la surcharge Implicite (en utilisant le très pratique %timeit du très pratique IPython)

In [7]: %timeit for e, o in Zip(five, five): pass
4.21 ms ± 39.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Ensuite, nous utilisons les différentes implémentations, l'une après l'autre

In [8]: %timeit for e, o in Zip(*partition_fu(p, ten)): pass
53.9 ms ± 112 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [9]: %timeit for e, o in Zip(*partition_it(p, ten)): pass
44.5 ms ± 3.84 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [10]: %timeit for e, o in Zip(*partition_li(p, ten)): pass
36.3 ms ± 101 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [11]: %timeit for e, o in Zip(*partition_li_alt(p, ten)): pass
37.3 ms ± 109 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [12]:

Commentaires

La plus simple des approches est aussi la plus rapide.

Utiliser l'astuce x[p(n)] est, ehm, inutile car à chaque étape vous Devez indexer une structure de données, ce qui vous donne un léger pénalité - c'est C'est bien de savoir si vouloir persuader un survivant d'une culture en déclin à pythoniser.

L’approche fonctionnelle, c’est-à-dire de manière opérationnelle équivalente à l’implémentation de Alternative append, est environ 50% plus lente, probablement en raison de Le fait que nous ayons une valeur supplémentaire (w/r évaluation des prédicats) function appelle pour chaque élément de la liste.

L’approche itertools présente les avantages (habituels) que non Liste potentiellement longue est instanciée et que la liste d’entrée n’est pas Entièrement traitée si vous sortez de la boucle consommateur, mais lorsque l'utiliser est plus lent en raison de la nécessité d'appliquer le prédicat aux deux extrémités de de la tee

De côté

Je suis tombé en amour avec l'idiome object.mutate() or object qui A été exposé par Marii Dans leur réponse montrant Une approche fonctionnelle du problème - I 'ai peur que, tôt ou tard, Je vais en abuser.

Notes de bas de page

[1] Accepté et le plus voté aujourd'hui, 14 septembre 2017 - mais bien sûr, j'ai les plus grands espoirs pour cette réponse!

3
gboffi

Je viens d'avoir exactement cette exigence. Je ne suis pas passionné par la recette d'itertools car elle implique deux passages distincts dans les données. Voici ma mise en œuvre:

def filter_twoway(test, data):
    "Like filter(), but returns the passes AND the fails as two separate lists"
    collected = {True: [], False: []}
    for datum in data:
        collected[test(datum)].append(datum)
    return (collected[True], collected[False])
2
Dan Stowell
from itertools import ifilterfalse

def filter2(predicate, iterable):
    return filter(predicate, iterable), list(ifilterfalse(predicate, iterable))
2
Tamás

Vous pouvez regarder Django.utils.functional.partition solution:

def partition(predicate, values):
    """
    Splits the values into two sets, based on the return value of the function
    (True/False). e.g.:

        >>> partition(lambda x: x > 3, range(5))
        [0, 1, 2, 3], [4]
    """
    results = ([], [])
    for item in values:
        results[predicate(item)].append(item)
    return results

À mon avis, c’est le plus élégant solution présentée ici.

Cette partie n'est pas documentée, seul le code source est disponible sur https://docs.djangoproject.com/fr/dev/_modules/Django/utils/functional/

2
vishes_shell

Tout le monde semble penser que leur solution est la meilleure, alors j'ai décidé d'utiliser timeit pour toutes les tester. J'ai utilisé "def is_odd (x): return x & 1" en tant que fonction de prédicat et "xrange (1000)" en tant qu'itérable. Voici ma version de Python:

Python 2.7.3 (v2.7.3:70274d53c1dd, Apr  9 2012, 20:52:43) 
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin

Et voici les résultats de mes tests:

Mark Byers
1000 loops, best of 3: 325 usec per loop

cldy
1000 loops, best of 3: 1.96 msec per loop

Dan S
1000 loops, best of 3: 412 usec per loop

TTimo
1000 loops, best of 3: 503 usec per loop

Ceux-ci sont tous comparables les uns aux autres. Essayons maintenant d'utiliser l'exemple donné dans la documentation Python.

import itertools

def partition(pred, iterable,
              # Optimized by replacing global lookups with local variables
              # defined as default values.
              filter=itertools.ifilter,
              filterfalse=itertools.ifilterfalse,
              tee=itertools.tee):
    '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)

Cela semble être un peu plus rapide.

100000 loops, best of 3: 2.58 usec per loop

L'exemple de code d'itertools bat tous les arrivants par un facteur d'au moins 100! La morale est de ne pas continuer à réinventer la roue.

1
samwyse

Code concis pour l'ajout à la liste de cibles

    def partition(cond,inputList):
        a,b= [],[]
        for item in inputList:
            target = a if cond(item) else b
            target.append(item)
        return a, b


    >>> a, b= partition(lambda x: x > 10,[1,4,12,7,42])
    >>> a
    [12, 42]
    >>> b
    [1, 4, 7]
0
Saurabh

Beaucoup de bonnes réponses déjà. J'aime utiliser ceci:

def partition( pred, iterable ):
    def _dispatch( ret, v ):
        if ( pred( v ) ):
            ret[0].append( v )
        else:
            ret[1].append( v )
        return ret
    return reduce( _dispatch, iterable, ( [], [] ) )

if ( __== '__main__' ):
    import random
    seq = range( 20 )
    random.shuffle( seq )
    print( seq )
    print( partition( lambda v : v > 10, seq ) )
0
TTimo