Python: tuple / dizionari come chiavi, seleziona, ordina


104

supponiamo di avere quantità di frutta di diversi colori, ad esempio, 24 banane blu, 12 mele verdi, 0 fragole blu e così via. Mi piacerebbe organizzarli in una struttura dati in Python che consenta una facile selezione e ordinamento. La mia idea era di inserirli in un dizionario con tuple come chiavi, ad es.

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

o anche dizionari, ad esempio,

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

Vorrei recuperare un elenco di tutti i frutti blu, o banane di tutti i colori, ad esempio, o ordinare questo dizionario in base al nome del frutto. Ci sono modi per farlo in modo pulito?

Potrebbe benissimo essere che i dizionari con tuple come chiavi non siano il modo corretto per gestire questa situazione.

Tutti i suggerimenti sono benvenuti!


26
Sembra che tu voglia un database ...
Adam Rosenfield

4
Faresti meglio a definire un clsas per modellare questi dati, piuttosto che cercare di coordinare diverse raccolte di questi valori
Cuga

2
@AdamRosenfield forse ne sta costruendo uno.
Prof.Falken

Volevo solo aggiungere che un dizionario non è hash quindi la seconda sintassi di cui chiedi non è possibile perché {'fruit': 'banana', 'color': 'blue'} cheèun dizionario non può essere usata come chiave per un altro dizionario. causerebbe un'eccezione TypeError: unhashable type: 'dict'.
epeleg

Risposte:


147

Personalmente, una delle cose che amo di Python è la combinazione tuple-dict. Quello che hai qui è effettivamente un array 2d (dove x = nome del frutto ey = colore), e generalmente sono un sostenitore del dettato delle tuple per l'implementazione di array 2d, almeno quando qualcosa di simile numpyo un database non è più appropriato . Quindi, in breve, penso che tu abbia un buon approccio.

Nota che non puoi usare i dict come chiavi in ​​un dict senza fare del lavoro extra, quindi non è una soluzione molto buona.

Detto questo, dovresti anche considerare namedtuple () . In questo modo potresti farlo:

>>> 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'

Ora puoi usare il tuo dict di conteggio della frutta:

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

Altri trucchi:

>>> 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')]

Facendo eco a chmullig, per ottenere un elenco di tutti i colori di un frutto, dovresti filtrare le chiavi, cioè

bananas = [fruit for fruit in fruits if fruit.name=='banana']

#senderle Hai scritto come commento a un'altra risposta "Ma la mia sensazione è che un database sia eccessivo per le esigenze dell'OP;"; Quindi preferisci creare una sottoclasse denominata. Ma cos'altro sono le istanze di classi se non micro-database con i propri strumenti per elaborare i propri dati?
eyquem

Potrei estrarre da quelle sottoliste con name='banana'?
Nico Schlömer

2
Come ha sottolineato chmullig, dovresti filtrare le chiavi, cioè bananas = filter(lambda fruit: fruit.name=='banana', fruits)o bananas = [fruit for fruit in fruits if fruit.name=='banana']. Questo è un modo in cui i dict annidati sono potenzialmente più efficienti; dipende tutto dal modo in cui prevedi di utilizzare i dati.
senderle

l'aggiunta di una chiave in più nella tupla denominata non renderebbe le cose più semplici? Direi di aggiungere un nuovo attributocount
openrijal

18

La tua migliore opzione sarà creare una semplice struttura di dati per modellare ciò che hai. Quindi puoi memorizzare questi oggetti in un semplice elenco e ordinarli / recuperarli come preferisci.

In questo caso, userei la seguente 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)

Quindi puoi semplicemente costruire istanze "Fruit" e aggiungerle a un elenco, come mostrato nel modo seguente:

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

L'elenco semplice fruitssarà molto più semplice, meno confuso e gestito meglio.

Alcuni esempi di utilizzo:

Tutti gli output di seguito sono il risultato dopo aver eseguito lo snippet di codice specificato seguito da:

for fruit in fruits:
    print fruit

Elenco non ordinato:

Displays:

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

Ordinati alfabeticamente per nome:

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

Displays:

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

Ordinati per quantità:

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

Displays:

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

Dove colore == rosso:

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

Displays:

Name: apple, Color: red, Quantity: 12

17

Database, dict of dicts, dizionario della lista dei dizionari, chiamato tuple (è una sottoclasse), sqlite, ridondanza ... non credevo ai miei occhi. Cos'altro ?

"Può darsi che i dizionari con le tuple come chiavi non siano il modo corretto per gestire questa situazione."

"La mia sensazione viscerale è che un database sia eccessivo per le esigenze dell'OP;"

Si! ho pensato

Quindi, secondo me, un elenco di tuple è sufficiente:

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

risultato

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

1
Ciao, mi piace la tua soluzione ma non affronta problemi di complessità operativa. tutti i tipi di ricerca sono di linea (O (n)) nella dimensione dell'elenco. mentre avrebbe senso che l'OP volesse che alcune azioni fossero più veloci di altre (ad esempio, ottenere il conteggio della banana gialla sarebbe qualcosa che mi sarei aspettato fosse possibile in O (1).
epeleg

13

Un dizionario probabilmente non è quello che dovresti usare in questo caso. Una libreria più completa sarebbe un'alternativa migliore. Probabilmente un vero database. Il più semplice sarebbe sqlite . Puoi tenere tutto in memoria passando la stringa ": memory:" invece di un nome di file.

Se vuoi continuare su questo percorso, puoi farlo con gli attributi extra nella chiave o nel valore. Tuttavia un dizionario non può essere la chiave per un altro dizionario, ma una tupla sì. I documenti spiegano cosa è consentito. Deve essere un oggetto immutabile, che include stringhe, numeri e tuple che contengono solo stringhe e numeri (e più tuple contenenti solo quei tipi in modo ricorsivo ...).

Potresti fare il tuo primo esempio con d = {('apple', 'red') : 4}, ma sarà molto difficile cercare ciò che desideri. Avresti bisogno di fare qualcosa del genere:

#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')]

4
Non ho e non ho votato in basso questa risposta, perché su scale più grandi i database sono (ovviamente!) Il modo migliore per andare. Ma la mia sensazione è che un database sia eccessivo per le esigenze dell'OP; forse questo spiega il downvote?
mittente

4

Con le chiavi come tuple, basta filtrare le chiavi con un secondo componente e ordinarlo:

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

L'ordinamento funziona perché le tuple hanno un ordinamento naturale se i loro componenti hanno un ordinamento naturale.

Con le chiavi come oggetti piuttosto completi, filtra semplicemente per k.color == 'blue'.

Non puoi davvero usare i dict come chiavi, ma puoi creare una classe più semplice come class Foo(object): passe aggiungere eventuali attributi al volo:

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

Queste istanze possono servire come chiavi di comando, ma attenzione alla loro mutevolezza!


3

Potresti avere un dizionario in cui le voci sono un elenco di altri dizionari:

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

Produzione:

{'banana': [{'yellow': 24}], 'apple': [{'red': 12}, {'green': 14}]}

Modifica: come ha sottolineato eumiro, potresti usare un dizionario di dizionari:

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

Produzione:

{'banana': {'yellow': 24}, 'apple': {'green': 14, 'red': 12}}


2
Dizionario della lista dei dizionari? Forse il dizionario del dizionario sarebbe sufficiente?
eumiro

@eumiro: Grazie, hai ragione, ed era la mia idea originale. Tuttavia, l'ho trasformato in un dict di elenchi di dict durante la codifica dell'esempio originale. Ho aggiunto un esempio di dict of dicts.
GreenMatt

I dizionari annidati tendono a creare confusione. Si prega di vedere la mia risposta
Cuga

@Cuga: sono d'accordo che i dettami, ecc. Possono creare confusione. Sto solo fornendo un esempio illustrativo per rispondere alla domanda di @Nico come chiesto.
GreenMatt

Mi scuso: non volevo implicare che la tua soluzione fosse sbagliata; chiaramente funziona e in alcune situazioni potrebbe essere l'ideale. Volevo condividere la mia opinione sulla situazione.
Cuga

2

Questo tipo di dati viene estratto in modo efficiente da una struttura dati simile a Trie. Consente inoltre uno smistamento veloce. L'efficienza della memoria potrebbe non essere così eccezionale.

Un trie tradizionale memorizza ogni lettera di una parola come un nodo dell'albero. Ma nel tuo caso il tuo "alfabeto" è diverso. Stai memorizzando stringhe invece di caratteri.

potrebbe assomigliare a questo:

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

vedere questo collegamento: trie in python


2

Vuoi usare due chiavi in ​​modo indipendente, quindi hai due scelte:

  1. Memorizza i dati in modo ridondante con due dict come {'banana' : {'blue' : 4, ...}, .... }e {'blue': {'banana':4, ...} ...}. Quindi, la ricerca e l'ordinamento sono facili, ma devi assicurarti di modificare i dettami insieme.

  2. Memorizzalo solo un dict, quindi scrivi le funzioni che ripetono su di essi, ad esempio:

    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()]

Non riesco a capire perché il codice nella mia risposta non viene visualizzato nel formato corretto. Ho provato a modificare e contrassegnare le ultime due righe come codice, ma non funziona!
highBandWidth

1
hai creato un elenco numerato e il parser interpreta il codice (4 spazi rientrati) come una continuazione del secondo elemento di quell'elenco. Rientra il codice di altri 4 spazi per un totale di 8 e il parser riconoscerà il codice come codice e lo formatterà correttamente.
mittente
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.