Python: tuplas / dicionários como chaves, selecionar, classificar

104

suponha que eu tenha quantidades de frutas de cores diferentes, por exemplo, 24 bananas azuis, 12 maçãs verdes, 0 morangos azuis e assim por diante. Eu gostaria de organizá-los em uma estrutura de dados em Python que permite fácil seleção e classificação. Minha ideia era colocá-los em um dicionário com tuplas como chaves, por exemplo,

{ ('banana',    'blue' ): 24,
  ('apple',     'green'): 12,
  ('strawberry','blue' ): 0,
  ...
}

ou mesmo dicionários, por exemplo,

{ {'fruit': 'banana',    'color': 'blue' }: 24,
  {'fruit': 'apple',     'color': 'green'}: 12,
  {'fruit': 'strawberry','color': 'blue' }: 0,
  ...
}

Gostaria de recuperar uma lista de todas as frutas azuis, ou bananas de todas as cores, por exemplo, ou classificar este dicionário pelo nome da fruta. Existem maneiras de fazer isso de maneira limpa?

Pode ser que dicionários com tuplas como chaves não sejam a maneira adequada de lidar com essa situação.

Todas as sugestões são bem-vindas!

Nico Schlömer
fonte
26
Parece que você quer um banco de dados ...
Adam Rosenfield
4
Seria melhor definir um clsas para modelar esses dados, em vez de tentar coordenar diferentes coleções desses valores
Cuga
2
@AdamRosenfield talvez ele esteja construindo um.
Prof. Falken
Só queria adicionar que um dicionário não é hashable, então a segunda sintaxe sobre a qual perguntar não é possível porque {'fruta': 'banana', 'cor': 'azul'} que é um dicionário não pode ser usada como uma chave para outro dicionário. isso causaria um TypeError: tipo inalterável: 'dict'.
epeleg de

Respostas:

147

Pessoalmente, uma das coisas que adoro em python é a combinação tupla-dicionário. O que você tem aqui é efetivamente um array 2d (onde x = nome da fruta ey = cor), e geralmente sou um defensor do ditado de tuplas para implementar arrays 2d, pelo menos quando algo como numpyum banco de dados não é mais apropriado . Resumindo, acho que você tem uma boa abordagem.

Observe que você não pode usar dicts como chaves em um dict sem fazer algum trabalho extra, então essa não é uma solução muito boa.

Dito isso, você também deve considerar namedtuple () . Dessa forma, você pode fazer isso:

>>> from collections import namedtuple
>>> Fruit = namedtuple("Fruit", ["name", "color"])
>>> f = Fruit(name="banana", color="red")
>>> print f
Fruit(name='banana', color='red')
>>> f.name
'banana'
>>> f.color
'red'

Agora você pode usar seu ditado de contagem de frutas:

>>> fruitcount = {Fruit("banana", "red"):5}
>>> fruitcount[f]
5

Outros truques:

>>> fruits = fruitcount.keys()
>>> fruits.sort()
>>> print fruits
[Fruit(name='apple', color='green'), 
 Fruit(name='apple', color='red'), 
 Fruit(name='banana', color='blue'), 
 Fruit(name='strawberry', color='blue')]
>>> fruits.sort(key=lambda x:x.color)
>>> print fruits
[Fruit(name='banana', color='blue'), 
 Fruit(name='strawberry', color='blue'), 
 Fruit(name='apple', color='green'), 
 Fruit(name='apple', color='red')]

Ecoando chmullig, para obter uma lista de todas as cores de uma fruta, você teria que filtrar as chaves, ou seja,

bananas = [fruit for fruit in fruits if fruit.name=='banana']
remetente
fonte
#senderle Você escreveu como comentário para outra resposta "Mas meu pressentimento é que um banco de dados é um exagero para as necessidades do OP;"; Portanto, você prefere criar uma subclasse dupla nomeada. Mas o que mais são instâncias de classes senão micro-bancos de dados com suas próprias ferramentas para processar seus dados?
eyquem
Eu poderia extrair dessas sublistas com name='banana'?
Nico Schlömer
2
Como chmullig apontou, você teria que filtrar as chaves, bananas = filter(lambda fruit: fruit.name=='banana', fruits)ou seja, ou bananas = [fruit for fruit in fruits if fruit.name=='banana']. Essa é uma maneira pela qual dicts aninhados são potencialmente mais eficientes; tudo se resume à maneira como você planeja usar os dados.
remetente em
adicionar uma chave a mais na tupla nomeada não tornaria as coisas mais fáceis? Eu diria que adicionaria um novo atributocount
openrijal
18

Sua melhor opção será criar uma estrutura de dados simples para modelar o que você tem. Em seguida, você pode armazenar esses objetos em uma lista simples e classificá-los / recuperá-los da maneira que desejar.

Para este caso, eu usaria a seguinte classe:

class Fruit:
    def __init__(self, name, color, quantity): 
        self.name = name
        self.color = color
        self.quantity = quantity

    def __str__(self):
        return "Name: %s, Color: %s, Quantity: %s" % \
     (self.name, self.color, self.quantity)

Em seguida, você pode simplesmente construir instâncias de "Fruit" e adicioná-las a uma lista, conforme mostrado da seguinte maneira:

fruit1 = Fruit("apple", "red", 12)
fruit2 = Fruit("pear", "green", 22)
fruit3 = Fruit("banana", "yellow", 32)
fruits = [fruit3, fruit2, fruit1] 

A lista simples fruitsserá muito mais fácil, menos confusa e com melhor manutenção.

Alguns exemplos de uso:

Todas as saídas abaixo são o resultado após a execução do snippet de código fornecido seguido por:

for fruit in fruits:
    print fruit

Lista não classificada:

Monitores:

Name: banana, Color: yellow, Quantity: 32
Name: pear, Color: green, Quantity: 22
Name: apple, Color: red, Quantity: 12

Classificados em ordem alfabética por nome:

fruits.sort(key=lambda x: x.name.lower())

Monitores:

Name: apple, Color: red, Quantity: 12
Name: banana, Color: yellow, Quantity: 32
Name: pear, Color: green, Quantity: 22

Ordenado por quantidade:

fruits.sort(key=lambda x: x.quantity)

Monitores:

Name: apple, Color: red, Quantity: 12
Name: pear, Color: green, Quantity: 22
Name: banana, Color: yellow, Quantity: 32

Onde cor == vermelho:

red_fruit = filter(lambda f: f.color == "red", fruits)

Monitores:

Name: apple, Color: red, Quantity: 12
Cuga
fonte
17

Banco de dados, dicionário de dictes, dicionário de lista de dicionários, denominado tupla (é uma subclasse), sqlite, redundância ... Não acreditei nos meus olhos. O quê mais ?

"Pode ser que dicionários com tuplas como chaves não sejam a maneira adequada de lidar com essa situação."

"meu pressentimento é que um banco de dados é um exagero para as necessidades do OP;"

sim! eu pensei

Então, na minha opinião, uma lista de tuplas é suficiente:

from operator import itemgetter

li = [  ('banana',     'blue'   , 24) ,
        ('apple',      'green'  , 12) ,
        ('strawberry', 'blue'   , 16 ) ,
        ('banana',     'yellow' , 13) ,
        ('apple',      'gold'   , 3 ) ,
        ('pear',       'yellow' , 10) ,
        ('strawberry', 'orange' , 27) ,
        ('apple',      'blue'   , 21) ,
        ('apple',      'silver' , 0 ) ,
        ('strawberry', 'green'  , 4 ) ,
        ('banana',     'brown'  , 14) ,
        ('strawberry', 'yellow' , 31) ,
        ('apple',      'pink'   , 9 ) ,
        ('strawberry', 'gold'   , 0 ) ,
        ('pear',       'gold'   , 66) ,
        ('apple',      'yellow' , 9 ) ,
        ('pear',       'brown'  , 5 ) ,
        ('strawberry', 'pink'   , 8 ) ,
        ('apple',      'purple' , 7 ) ,
        ('pear',       'blue'   , 51) ,
        ('chesnut',    'yellow',  0 )   ]


print set( u[1] for u in li ),': all potential colors'
print set( c for f,c,n in li if n!=0),': all effective colors'
print [ c for f,c,n in li if f=='banana' ],': all potential colors of bananas'
print [ c for f,c,n in li if f=='banana' and n!=0],': all effective colors of bananas'
print

print set( u[0] for u in li ),': all potential fruits'
print set( f for f,c,n in li if n!=0),': all effective fruits'
print [ f for f,c,n in li if c=='yellow' ],': all potential fruits being yellow'
print [ f for f,c,n in li if c=='yellow' and n!=0],': all effective fruits being yellow'
print

print len(set( u[1] for u in li )),': number of all potential colors'
print len(set(c for f,c,n in li if n!=0)),': number of all effective colors'
print len( [c for f,c,n in li if f=='strawberry']),': number of potential colors of strawberry'
print len( [c for f,c,n in li if f=='strawberry' and n!=0]),': number of effective colors of strawberry'
print

# sorting li by name of fruit
print sorted(li),'  sorted li by name of fruit'
print

# sorting li by number 
print sorted(li, key = itemgetter(2)),'  sorted li by number'
print

# sorting li first by name of color and secondly by name of fruit
print sorted(li, key = itemgetter(1,0)),'  sorted li first by name of color and secondly by name of fruit'
print

resultado

set(['blue', 'brown', 'gold', 'purple', 'yellow', 'pink', 'green', 'orange', 'silver']) : all potential colors
set(['blue', 'brown', 'gold', 'purple', 'yellow', 'pink', 'green', 'orange']) : all effective colors
['blue', 'yellow', 'brown'] : all potential colors of bananas
['blue', 'yellow', 'brown'] : all effective colors of bananas

set(['strawberry', 'chesnut', 'pear', 'banana', 'apple']) : all potential fruits
set(['strawberry', 'pear', 'banana', 'apple']) : all effective fruits
['banana', 'pear', 'strawberry', 'apple', 'chesnut'] : all potential fruits being yellow
['banana', 'pear', 'strawberry', 'apple'] : all effective fruits being yellow

9 : number of all potential colors
8 : number of all effective colors
6 : number of potential colors of strawberry
5 : number of effective colors of strawberry

[('apple', 'blue', 21), ('apple', 'gold', 3), ('apple', 'green', 12), ('apple', 'pink', 9), ('apple', 'purple', 7), ('apple', 'silver', 0), ('apple', 'yellow', 9), ('banana', 'blue', 24), ('banana', 'brown', 14), ('banana', 'yellow', 13), ('chesnut', 'yellow', 0), ('pear', 'blue', 51), ('pear', 'brown', 5), ('pear', 'gold', 66), ('pear', 'yellow', 10), ('strawberry', 'blue', 16), ('strawberry', 'gold', 0), ('strawberry', 'green', 4), ('strawberry', 'orange', 27), ('strawberry', 'pink', 8), ('strawberry', 'yellow', 31)]   sorted li by name of fruit

[('apple', 'silver', 0), ('strawberry', 'gold', 0), ('chesnut', 'yellow', 0), ('apple', 'gold', 3), ('strawberry', 'green', 4), ('pear', 'brown', 5), ('apple', 'purple', 7), ('strawberry', 'pink', 8), ('apple', 'pink', 9), ('apple', 'yellow', 9), ('pear', 'yellow', 10), ('apple', 'green', 12), ('banana', 'yellow', 13), ('banana', 'brown', 14), ('strawberry', 'blue', 16), ('apple', 'blue', 21), ('banana', 'blue', 24), ('strawberry', 'orange', 27), ('strawberry', 'yellow', 31), ('pear', 'blue', 51), ('pear', 'gold', 66)]   sorted li by number

[('apple', 'blue', 21), ('banana', 'blue', 24), ('pear', 'blue', 51), ('strawberry', 'blue', 16), ('banana', 'brown', 14), ('pear', 'brown', 5), ('apple', 'gold', 3), ('pear', 'gold', 66), ('strawberry', 'gold', 0), ('apple', 'green', 12), ('strawberry', 'green', 4), ('strawberry', 'orange', 27), ('apple', 'pink', 9), ('strawberry', 'pink', 8), ('apple', 'purple', 7), ('apple', 'silver', 0), ('apple', 'yellow', 9), ('banana', 'yellow', 13), ('chesnut', 'yellow', 0), ('pear', 'yellow', 10), ('strawberry', 'yellow', 31)]   sorted li first by name of color and secondly by name of fruit
eyquem
fonte
1
Olá, gostei da sua solução, mas ela não aborda questões de complexidade operacional. todos os tipos de pesquisa são lineares (O (n)) no tamanho da lista. enquanto faria sentido que o OP quisesse que algumas ações fossem mais rápidas do que outras (por exemplo, obter a contagem de banana amarela seria algo que eu esperaria ser possível em O (1).
epeleg
13

Provavelmente, um dicionário não é o que você deveria usar neste caso. Uma biblioteca com mais recursos seria uma alternativa melhor. Provavelmente um banco de dados real. O mais fácil seria sqlite . Você pode manter tudo na memória passando a string ': memory:' em vez de um nome de arquivo.

Se você quiser continuar por este caminho, poderá fazê-lo com os atributos extras na chave ou no valor. No entanto, um dicionário não pode ser a chave para outro dicionário, mas uma tupla pode. Os documentos explicam o que é permitido. Deve ser um objeto imutável, que inclui strings, números e tuplas que contêm apenas strings e números (e mais tuplas contendo apenas esses tipos recursivamente ...).

Você poderia fazer seu primeiro exemplo com d = {('apple', 'red') : 4}, mas será muito difícil consultar o que deseja. Você precisaria fazer algo assim:

#find all apples
apples = [d[key] for key in d.keys() if key[0] == 'apple']

#find all red items
red = [d[key] for key in d.keys() if key[1] == 'red']

#the red apple
redapples = d[('apple', 'red')]
chmullig
fonte
4
Eu não votei e não votaria nessa resposta, porque em escalas maiores os bancos de dados são (obviamente!) O melhor caminho a seguir. Mas meu pressentimento é que um banco de dados é um exagero para as necessidades do OP; talvez isso explique o downvote?
remetente
4

Com chaves como tuplas, basta filtrar as chaves com determinado segundo componente e classificá-lo:

blue_fruit = sorted([k for k in data.keys() if k[1] == 'blue'])
for k in blue_fruit:
  print k[0], data[k] # prints 'banana 24', etc

A classificação funciona porque as tuplas têm ordem natural se seus componentes têm ordem natural.

Com as chaves como objetos completos, basta filtrar k.color == 'blue'.

Você realmente não pode usar dicts como chaves, mas pode criar uma classe mais simples como class Foo(object): passe adicionar quaisquer atributos a ela instantaneamente:

k = Foo()
k.color = 'blue'

Essas instâncias podem servir como chaves de ditado, mas cuidado com sua mutabilidade!

9000
fonte
3

Você pode ter um dicionário onde as entradas são uma lista de outros dicionários:

fruit_dict = dict()
fruit_dict['banana'] = [{'yellow': 24}]
fruit_dict['apple'] = [{'red': 12}, {'green': 14}]
print fruit_dict

Resultado:

{'banana': [{'amarelo': 24}], 'maçã': [{'vermelho': 12}, {'verde': 14}]}

Edit: Como eumiro apontou, você poderia usar um dicionário de dicionários:

fruit_dict = dict()
fruit_dict['banana'] = {'yellow': 24}
fruit_dict['apple'] = {'red': 12, 'green': 14}
print fruit_dict

Resultado:

{'banana': {'amarelo': 24}, 'maçã': {'verde': 14, 'vermelho': 12}}

GreenMatt
fonte
2
Dicionário de lista de dicionários? Talvez um dicionário de dicionário fosse suficiente?
eumiro
@eumiro: Obrigado, você está certo, e essa foi minha ideia original. No entanto, transformei isso em um dicionário de listas de dictos enquanto codificava o exemplo original. Eu adicionei um exemplo de dict of dicts.
GreenMatt
Dicionários aninhados tendem a ser confusos. Por favor, veja minha resposta
Cuga
@Cuga: Eu concordo que dictos de dictos etc. podem ser confusos. Estou apenas fornecendo um exemplo ilustrativo para responder à pergunta de @Nico conforme solicitado.
GreenMatt
Peço desculpas: não quis dizer que sua solução está errada; claramente funciona e em algumas situações pode ser o ideal. Eu queria compartilhar minha opinião sobre a situação.
Cuga
2

Este tipo de dados é extraído com eficiência de uma estrutura de dados do tipo Trie. Também permite uma classificação rápida. A eficiência da memória pode não ser tão grande.

Um trie tradicional armazena cada letra de uma palavra como um nó na árvore. Mas no seu caso, o seu "alfabeto" é diferente. Você está armazenando strings em vez de caracteres.

pode ser parecido com isto:

root:                Root
                     /|\
                    / | \
                   /  |  \     
fruit:       Banana Apple Strawberry
              / |      |     \
             /  |      |      \
color:     Blue Yellow Green  Blue
            /   |       |       \
           /    |       |        \
end:      24   100      12        0

veja este link: trie em python

Scott Morken
fonte
2

Você deseja usar duas chaves independentemente, então você tem duas opções:

  1. Armazene os dados de forma redundante com dois dicts como {'banana' : {'blue' : 4, ...}, .... }e {'blue': {'banana':4, ...} ...}. Então, pesquisar e classificar é fácil, mas você deve certificar-se de modificar os dictos juntos.

  2. Armazene apenas um dicionário e, em seguida, escreva funções que iterem sobre eles, por exemplo:

    d = {'banana' : {'blue' : 4, 'yellow':6}, 'apple':{'red':1} }
    
    blueFruit = [(fruit,d[fruit]['blue']) if d[fruit].has_key('blue') for fruit in d.keys()]
alta largura de banda
fonte
Não consigo descobrir por que o código em minha resposta não aparece no formato correto. Tentei editar e marcar as duas últimas linhas como código, mas não funcionou!
highBandWidth
1
você criou uma lista numerada e o analisador está interpretando o código (4 espaços indentados) como uma continuação do segundo item dessa lista. Recue o código mais 4 espaços para um total de 8, e o analisador irá reconhecer o código como código e formatá-lo corretamente.
remetente em