Python: Tupel / Wörterbücher als Schlüssel, auswählen, sortieren

104

Angenommen, ich habe Mengen von Früchten in verschiedenen Farben, z. B. 24 blaue Bananen, 12 grüne Äpfel, 0 blaue Erdbeeren und so weiter. Ich möchte sie in einer Datenstruktur in Python organisieren, die eine einfache Auswahl und Sortierung ermöglicht. Meine Idee war, sie in ein Wörterbuch mit Tupeln als Schlüssel zu schreiben, z.

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

oder sogar Wörterbücher, z.

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

Ich möchte zum Beispiel eine Liste aller blauen Früchte oder Bananen aller Farben abrufen oder dieses Wörterbuch nach dem Namen der Frucht sortieren. Gibt es Möglichkeiten, dies auf saubere Weise zu tun?

Es kann durchaus sein, dass Wörterbücher mit Tupeln als Schlüssel nicht der richtige Weg sind, um mit dieser Situation umzugehen.

Alle Vorschläge willkommen!

Nico Schlömer
quelle
26
Es klingt wie Sie eine Datenbank wollen ...
Adam Rosenfield
4
Am besten definieren Sie eine Klasse, um diese Daten zu modellieren, anstatt zu versuchen, verschiedene Sammlungen dieser Werte zu koordinieren
Cuga
2
@ AdamRosenfield, vielleicht baut er einen.
Prof. Falken
Ich wollte nur hinzufügen, dass ein Wörterbuch nicht hashbar ist, sodass die zweite Syntax, nach der Sie fragen, nicht möglich ist, da {'Frucht': 'Banane', 'Farbe': 'Blau'}, ein Wörterbuch, nicht als Schlüssel verwendet werden kann für ein anderes Wörterbuch. es würde einen TypeError verursachen: unhashable type: 'dict'.
Epeleg

Antworten:

147

Persönlich ist eines der Dinge, die ich an Python liebe, die Tupel-Dikt-Kombination. Was Sie hier haben, ist effektiv ein 2d-Array (wobei x = Fruchtname und y = Farbe), und ich bin im Allgemeinen ein Befürworter des Diktats von Tupeln für die Implementierung von 2d-Arrays, zumindest wenn so etwas numpyoder eine Datenbank nicht geeigneter ist . Kurz gesagt, ich denke, Sie haben einen guten Ansatz.

Beachten Sie, dass Sie Diktate nicht als Schlüssel in einem Diktat verwenden können, ohne zusätzliche Arbeit zu leisten. Dies ist also keine sehr gute Lösung.

Sie sollten jedoch auch namedtuple () berücksichtigen . Auf diese Weise können Sie Folgendes tun:

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

Jetzt können Sie Ihr Obstzählungs-Diktat verwenden:

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

Andere Tricks:

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

Wenn Sie chmullig wiederholen, um eine Liste aller Farben einer Frucht zu erhalten, müssten Sie die Schlüssel filtern, d. H.

bananas = [fruit for fruit in fruits if fruit.name=='banana']
senderle
quelle
#senderle Sie haben als Kommentar zu einer anderen Antwort geschrieben: "Aber ich habe das Gefühl, dass eine Datenbank für die Bedürfnisse des OP übertrieben ist."; Sie ziehen es daher vor, eine Namedtuple-Unterklasse zu erstellen. Aber was sind andere Instanzen von Klassen, wenn nicht Mikrodatenbanken mit eigenen Tools zur Verarbeitung ihrer Daten?
Eyquem
Könnte ich aus diesen Extrakt-Unterlisten mit name='banana'?
Nico Schlömer
2
Wie chmullig betonte, müssten Sie die Schlüssel filtern, dh bananas = filter(lambda fruit: fruit.name=='banana', fruits)oder bananas = [fruit for fruit in fruits if fruit.name=='banana']. Dies ist eine Möglichkeit, mit der verschachtelte Dikte potenziell effizienter sind. Alles hängt davon ab, wie Sie die Daten verwenden möchten.
senderle
Würde das Hinzufügen eines weiteren Schlüssels im benannten Tupel die Sache nicht einfacher machen? Ich würde sagen, ein neues Attribut hinzufügencount
openrijal
18

Am besten erstellen Sie eine einfache Datenstruktur, um Ihre Daten zu modellieren. Anschließend können Sie diese Objekte in einer einfachen Liste speichern und nach Ihren Wünschen sortieren / abrufen.

In diesem Fall würde ich die folgende Klasse verwenden:

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)

Anschließend können Sie einfach "Fruit" -Instanzen erstellen und einer Liste hinzufügen, wie auf folgende Weise gezeigt:

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

Die einfache Liste fruitswird viel einfacher, weniger verwirrend und besser gepflegt.

Einige Anwendungsbeispiele:

Alle folgenden Ausgaben sind das Ergebnis nach dem Ausführen des angegebenen Code-Snippets, gefolgt von:

for fruit in fruits:
    print fruit

Unsortierte Liste:

Anzeigen:

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

Alphabetisch nach Namen sortiert:

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

Anzeigen:

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

Nach Menge sortiert:

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

Anzeigen:

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

Wo Farbe == rot:

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

Anzeigen:

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

Datenbank, Diktat der Diktate, Wörterbuch der Liste der Wörterbücher, benanntes Tupel (es ist eine Unterklasse), SQLite, Redundanz ... Ich traute meinen Augen nicht. Was sonst ?

"Es kann durchaus sein, dass Wörterbücher mit Tupeln als Schlüssel nicht der richtige Weg sind, um mit dieser Situation umzugehen."

"Mein Bauchgefühl ist, dass eine Datenbank für die Bedürfnisse des OP übertrieben ist."

Ja! ich dachte

Meiner Meinung nach reicht eine Liste von Tupeln aus:

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

Ergebnis

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
quelle
1
Hallo, ich mag Ihre Lösung, aber sie behandelt keine Probleme der Betriebskomplexität. Alle Suchtypen sind in der Größe der Liste Liner (O (n)). Es wäre zwar sinnvoll, wenn das OP wollte, dass einige Aktionen schneller sind als andere (z. B. würde ich erwarten, dass die gelbe
Bananenzahl
13

Ein Wörterbuch ist wahrscheinlich nicht das, was Sie in diesem Fall verwenden sollten. Eine Bibliothek mit mehr Funktionen wäre eine bessere Alternative. Wahrscheinlich eine echte Datenbank. Am einfachsten wäre SQLite . Sie können das Ganze im Speicher behalten, indem Sie anstelle eines Dateinamens die Zeichenfolge ': memory:' übergeben.

Wenn Sie diesen Pfad fortsetzen möchten, können Sie dies mit den zusätzlichen Attributen im Schlüssel oder im Wert tun. Ein Wörterbuch kann jedoch nicht der Schlüssel zu einem anderen Wörterbuch sein, sondern ein Tupel. In den Dokumenten wird erklärt, was zulässig ist. Es muss ein unveränderliches Objekt sein, das Zeichenfolgen, Zahlen und Tupel enthält, die nur Zeichenfolgen und Zahlen enthalten (und weitere Tupel, die nur diese Typen rekursiv enthalten ...).

Sie könnten Ihr erstes Beispiel mit machen d = {('apple', 'red') : 4}, aber es wird sehr schwierig sein, nach dem zu fragen, was Sie wollen. Sie müssten so etwas tun:

#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
quelle
4
Ich habe diese Antwort nicht abgelehnt und wollte sie auch nicht ablehnen, da Datenbanken in größerem Maßstab (offensichtlich!) Der beste Weg sind. Mein Bauchgefühl ist jedoch, dass eine Datenbank für die Bedürfnisse des OP übertrieben ist. Vielleicht erklärt das die Ablehnung?
senderle
4

Mit Schlüsseln als Tupel filtern Sie einfach die Schlüssel mit der angegebenen zweiten Komponente und sortieren sie:

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

Das Sortieren funktioniert, weil Tupel eine natürliche Reihenfolge haben, wenn ihre Komponenten eine natürliche Reihenfolge haben.

Mit Schlüsseln als eher vollwertigen Objekten filtern Sie einfach nach k.color == 'blue'.

Sie können Diktate nicht wirklich als Schlüssel verwenden, aber Sie können eine einfachste Klasse erstellen class Foo(object): passund im laufenden Betrieb beliebige Attribute hinzufügen:

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

Diese Instanzen können als Diktatschlüssel dienen, aber achten Sie auf ihre Veränderbarkeit!

9000
quelle
3

Sie könnten ein Wörterbuch haben, in dem die Einträge eine Liste anderer Wörterbücher enthalten:

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

Ausgabe:

{'Banane': [{'Gelb': 24}], 'Apfel': [{'Rot': 12}, {'Grün': ​​14}]}

Bearbeiten: Wie eumiro betonte, könnten Sie ein Wörterbuch von Wörterbüchern verwenden:

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

Ausgabe:

{'Banane': {'Gelb': 24}, 'Apfel': {'Grün': ​​14, 'Rot': 12}}

GreenMatt
quelle
2
Wörterbuch der Liste der Wörterbücher? Vielleicht würde ein Wörterbuch des Wörterbuchs ausreichen?
Eumiro
@eumiro: Danke, du hast recht, und das war meine ursprüngliche Idee. Ich habe es jedoch in ein Diktat von Diktatlisten verwandelt, während ich das ursprüngliche Beispiel codiert habe. Ich habe ein Diktatbeispiel hinzugefügt.
GreenMatt
Verschachtelte Wörterbücher sind in der Regel verwirrend. Bitte sehen Sie meine Antwort
Cuga
@Cuga: Ich stimme zu, dass Diktate von Diktaten usw. verwirrend werden können. Ich gebe nur ein anschauliches Beispiel, um die gestellte Frage von @ Nico zu beantworten.
GreenMatt
Ich entschuldige mich: Ich wollte nicht implizieren, dass Ihre Lösung falsch ist. es funktioniert eindeutig und in einigen Situationen könnte es das ideale sein. Ich wollte meine Sicht auf die Situation teilen.
Cuga
2

Diese Art von Daten wird effizient aus einer Trie-ähnlichen Datenstruktur abgerufen. Es ermöglicht auch eine schnelle Sortierung. Die Speichereffizienz ist jedoch möglicherweise nicht so hoch.

Ein traditioneller Versuch speichert jeden Buchstaben eines Wortes als Knoten im Baum. Aber in Ihrem Fall ist Ihr "Alphabet" anders. Sie speichern Zeichenfolgen anstelle von Zeichen.

es könnte ungefähr so ​​aussehen:

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

siehe diesen Link: trie in python

Scott Morken
quelle
2

Sie möchten zwei Tasten unabhängig voneinander verwenden, haben also zwei Möglichkeiten:

  1. Speichern Sie die Daten redundant mit zwei Dikten als {'banana' : {'blue' : 4, ...}, .... }und {'blue': {'banana':4, ...} ...}. Dann ist das Suchen und Sortieren einfach, aber Sie müssen sicherstellen, dass Sie die Diktate gemeinsam ändern.

  2. Speichern Sie es nur ein Diktat und schreiben Sie dann Funktionen, die über sie iterieren, z.

    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()]
grosse Bandbreite
quelle
Ich kann nicht herausfinden, warum der Code in meiner Antwort nicht im richtigen Format angezeigt wird. Ich habe versucht, die letzten beiden Zeilen als Code zu bearbeiten und zu markieren, aber es funktioniert nicht!
HighBandWidth
1
Sie haben eine nummerierte Liste erstellt, und der Parser interpretiert den Code (4 Leerzeichen eingerückt) als Fortsetzung des zweiten Elements dieser Liste. Einrücken des Codes um weitere 4 Leerzeichen für insgesamt 8, und der Parser erkennt den Code als Code und formatiert ihn korrekt.
senderle