Comment filtrer un dictionnaire selon une fonction de condition arbitraire?


212

J'ai un dictionnaire de points, disons:

>>> points={'a':(3,4), 'b':(1,2), 'c':(5,5), 'd':(3,3)}

Je veux créer un nouveau dictionnaire avec tous les points dont la valeur x et y est inférieure à 5, c'est-à-dire les points 'a', 'b' et 'd'.

Selon le livre , chaque dictionnaire a la items()fonction, qui retourne une liste de (key, pair) tuple:

>>> points.items()
[('a', (3, 4)), ('c', (5, 5)), ('b', (1, 2)), ('d', (3, 3))]

J'ai donc écrit ceci:

>>> for item in [i for i in points.items() if i[1][0]<5 and i[1][1]<5]:
...     points_small[item[0]]=item[1]
...
>>> points_small
{'a': (3, 4), 'b': (1, 2), 'd': (3, 3)}

Existe-t-il une manière plus élégante? Je m'attendais à ce que Python ait une fonction super géniale dictionary.filter(f)...


Réponses:


427

De nos jours, en Python 2.7 et supérieur, vous pouvez utiliser une compréhension de dict:

{k: v for k, v in points.iteritems() if v[0] < 5 and v[1] < 5}

Et en Python 3:

{k: v for k, v in points.items() if v[0] < 5 and v[1] < 5}

15
Upvote! C'est plus de deux fois plus rapide que l'approche plus générale de Martellis. Notez que vous pouvez également utiliser des vues (comme les iteitems, elles NE SONT PAS une copie des éléments dict): {k: v pour k, v dans points.viewitems () if v [0] <5 and v [1] < 5}
dorvak

5
Et voici une bonne explication pour laquelle l'appel de fonction dict () est plus lent que le constructeur / syntaxe littérale {} doughellmann.com/2012/11/…
dorvak

1
Gardez à l'esprit que cela a iteritemsété supprimé dans Python 3. Mais vous pouvez utiliser à la itemsplace. Il se comporte comme les iteritemsanciennes versions.
Elias Zamaria

1
@Datanovice, j'en suis sûr. On pourrait aussi ouvrir une nouvelle question avec suffisamment de détails pour obtenir une réponse plus utile;)
Thomas

1
On a ouvert une question avec des réponses limitées, on a donc eu recours à la lecture d'autant de questions que possible pour mieux comprendre. On en a vu un plus compétent et donc, j'ai continué à choisir ses cerveaux;) mon Q: stackoverflow.com/questions/50104127/…
Datanovice

110
dict((k, v) for k, v in points.items() if all(x < 5 for x in v))

Vous pouvez choisir d'appeler .iteritems()au lieu de .items()si vous êtes en Python 2 et pointspouvez avoir beaucoup d'entrées.

all(x < 5 for x in v)peut être exagéré si vous savez avec certitude que chaque point sera toujours 2D uniquement (dans ce cas, vous pourriez exprimer la même contrainte avec un and) mais cela fonctionnera bien ;-).


21
points_small = dict(filter(lambda (a,(b,c)): b<5 and c < 5, points.items()))

1
En Python 2, utilisez iteritems () au lieu d'éléments ()
Regisz

2
En python 3.5, cela renvoie une erreur: points_small = dict (filtre (lambda (a, (b, c)): b <5 et c <5, points.items ())) ^ SyntaxError: syntaxe invalide `
Mevin Babu

Je pense que ce n'est pas pris en charge en python 3
matanster

15
>>> points = {'a': (3, 4), 'c': (5, 5), 'b': (1, 2), 'd': (3, 3)}
>>> dict(filter(lambda x: (x[1][0], x[1][1]) < (5, 5), points.items()))

{'a': (3, 4), 'b': (1, 2), 'd': (3, 3)}

3
génial ! il convient de mentionner qu'il s'agit de Py3, car la lambda ne peut plus déballer l'argument tuple (voir PEP 3113 )
Ciprian Tomoiagă

Vous comparez les tuples lexicographiquement, ce qui n'est pas ce que l'OP exigeait. Dans votre cas, le point (3, 10)réussira le test: (3, 10) < (5, 5)est vrai, mais c'est faux ( ydevrait également être inférieur à 5).
dmitry_romanov

9
dict((k, v) for (k, v) in points.iteritems() if v[0] < 5 and v[1] < 5)

7

Je pense que la réponse d'Alex Martelli est certainement la façon la plus élégante de le faire, mais je voulais juste ajouter un moyen de satisfaire votre besoin d'une dictionary.filter(f)méthode super géniale d'une manière Pythonique:

class FilterDict(dict):
    def __init__(self, input_dict):
        for key, value in input_dict.iteritems():
            self[key] = value
    def filter(self, criteria):
        for key, value in self.items():
            if (criteria(value)):
                self.pop(key)

my_dict = FilterDict( {'a':(3,4), 'b':(1,2), 'c':(5,5), 'd':(3,3)} )
my_dict.filter(lambda x: x[0] < 5 and x[1] < 5)

Fondamentalement, nous créons une classe qui hérite de dict, mais ajoute la méthode de filtrage. Nous devons utiliser .items()pour le filtrage, car l'utilisation .iteritems()lors d'une itération destructrice soulèvera une exception.


+1 Merci, code élégant. Je pense vraiment que cela devrait faire partie du dictionnaire standard.
Adam Matan,

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.