Como filtrar um dicionário de acordo com uma função de condição arbitrária?


212

Eu tenho um dicionário de pontos, diga:

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

Quero criar um novo dicionário com todos os pontos cujo valor xey seja menor que 5, ou seja, pontos 'a', 'b' e 'd'.

De acordo com o livro , cada dicionário tem a items()função, que retorna uma lista de (key, pair) tupla:

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

Então eu escrevi isso:

>>> 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 uma maneira mais elegante? Eu estava esperando Python para ter alguma dictionary.filter(f)função super-impressionante ...


Respostas:


427

Atualmente, no Python 2.7 e superior, você pode usar uma compreensão de ditado:

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

E no Python 3:

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

15
Voto a favor! Isso é duas vezes mais rápido que a abordagem mais geral da Martellis. Observe que você também pode usar visualizações (como itens, eles NÃO são uma cópia dos itens do dict): {k: v para k, v em points.viewitems () se v [0] <5 e v [1] < 5}
dorvak 10/07/2013

5
E aqui está uma boa explicação por que o dict chamada de função () é mais lento que o construtor / literal sintaxe {} doughellmann.com/2012/11/...
dorvak

1
Lembre-se de que iteritemsfoi removido no Python 3. Mas você pode usá-lo items. Ele se comporta da maneira que iteritemsfunciona em versões mais antigas.
Elias Zamaria

1
@Datanovice Tenho certeza de que poderia. Pode-se também abrir uma nova pergunta com detalhes suficientes para obter uma resposta mais útil;) #
Thomas

1
Uma pessoa abriu uma pergunta com respostas limitadas; assim, recorreu à leitura de tantas perguntas quanto possível para obter uma melhor compreensão. Vimos um mais experiente e, portanto, continuamos a escolher
nossos

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

Você pode optar por ligar em .iteritems()vez de .items()se estiver no Python 2 e pointspode ter muitas entradas.

all(x < 5 for x in v)pode ser um exagero se você tiver certeza de que cada ponto será sempre apenas em 2D (nesse caso, você pode expressar a mesma restrição com um and), mas funcionará bem ;-).


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

1
Em Python 2 iteritems de uso () em vez de itens ()
Regisz

2
No python 3.5, isso retorna um erro: points_small = dict (filter (lambda (a, (b, c)): b <5 ec <5, points.items ())) ^ SyntaxError: sintaxe inválida `
Mevin Babu

Eu acho que não é suportado em python 3
matanster 19/04/19

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
ótimo ! vale a pena mencionar que este é PY3, como o lambda não pode descompactar o argumento tupla (veja PEP 3113 )
Ciprian Tomoiagă

Você compara tuplas lexicograficamente, o que não é o OP necessário. No seu caso, o point (3, 10)será aprovado no teste: (3, 10) < (5, 5)é True, mas está errado (também ydeve ser menor que 5).
Djdjdjjjjjjjjjj

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

7

Eu acho que a resposta de Alex Martelli é definitivamente a maneira mais elegante de fazer isso, mas só queria adicionar uma maneira de satisfazer sua necessidade de um dictionary.filter(f)método super impressionante de uma forma pitonica:

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)

Basicamente, criamos uma classe que herda de dict, mas adiciona o método de filtro. Precisamos usar .items()para a filtragem, pois o uso .iteritems()durante a iteração destrutiva gera exceção.


+1 Obrigado, código elegante. Eu realmente acho que deveria fazer parte do dicionário padrão.
Adam Matan

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.