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.
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
.
>>> 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.
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]}
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.
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.
Les différentes approches suggérées peuvent être classées En trois catégories,
lis.append
, renvoyant un 2 listes de listes,lis.append
à médiation par une approche fonctionnelle, renvoyant un 2 listes de listes,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
etfilter
, mais vous n'avez pas itéré Via l'entrée ou appelépred
une fois! L’avantage de la recetteitertools
est qu’elle ne matérialise aucune liste, ni ne regarde Plus loin que nécessaire dans la saisie. Il appellepred
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]:
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
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.
[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!
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])
from itertools import ifilterfalse
def filter2(predicate, iterable):
return filter(predicate, iterable), list(ifilterfalse(predicate, iterable))
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/
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.
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]
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 ) )