Python: Krotki / słowniki jako klucze, wybierz, sortuj

104

przypuśćmy, że mam wiele owoców w różnych kolorach, np. 24 niebieskie banany, 12 zielonych jabłek, 0 truskawek i tak dalej. Chciałbym uporządkować je w strukturę danych w Pythonie, która pozwala na łatwe wybieranie i sortowanie. Moim pomysłem było umieszczenie ich w słowniku z krotkami jako kluczami, np.

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

czy nawet słowniki, np.

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

Chciałbym na przykład pobrać listę wszystkich niebieskich owoców lub bananów we wszystkich kolorach lub posortować ten słownik według nazwy owocu. Czy są sposoby, aby to zrobić w czysty sposób?

Może się zdarzyć, że słowniki z kluczami krotek nie są właściwym sposobem radzenia sobie z taką sytuacją.

Wszystkie sugestie mile widziane!

Nico Schlömer
źródło
26
Wygląda na to, że potrzebujesz bazy danych ...
Adam Rosenfield
4
Najlepiej byłoby zdefiniować klasę, aby modelować te dane, zamiast próbować koordynować różne zbiory tych wartości
Cuga,
2
@AdamRosenfield może właśnie taki buduje.
Prof. Falken
Chciałem tylko dodać, że słownik nie jest hashowany, więc druga składnia, o którą pytasz, nie jest możliwa, ponieważ {'fruit': 'banana', 'color': 'blue'}, który jest słownikiem, nie może być używany jako klucz dla innego słownika. spowodowałoby to TypeError: unhashable type: 'dict'.
epeleg

Odpowiedzi:

147

Osobiście jedną z rzeczy, które uwielbiam w Pythonie, jest kombinacja tuple-dict. To, co tu masz, to w rzeczywistości tablica 2d (gdzie x = nazwa owocu, a y = kolor), a ja generalnie jestem zwolennikiem zasady krotek do implementacji tablic 2d, przynajmniej jeśli coś takiego numpylub baza danych nie jest bardziej odpowiednie . Krótko mówiąc, myślę, że masz dobre podejście.

Zauważ, że nie możesz używać dykt jako kluczy w dyktacie bez dodatkowej pracy, więc nie jest to zbyt dobre rozwiązanie.

To powiedziawszy, powinieneś również rozważyć namedtuple () . W ten sposób możesz to zrobić:

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

Teraz możesz użyć swojej dyktowania fruitcount:

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

Inne sztuczki:

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

Powtarzając chmullig, aby otrzymać listę wszystkich kolorów jednego owocu, należałoby przefiltrować klucze, tj

bananas = [fruit for fruit in fruits if fruit.name=='banana']
nadawca
źródło
#senderle Napisałeś jako komentarz do innej odpowiedzi "Ale mam przeczucie, że baza danych jest przesadna dla potrzeb PO;"; Dlatego wolisz tworzyć podklasę nazwanątuple. Ale czym innym są instancje klas, jeśli nie mikro-bazami danych z własnymi narzędziami do przetwarzania ich danych?
eyquem
Czy mógłbym z tych podlist wyciągów z name='banana'?
Nico Schlömer
2
Jak zauważył chmullig, należałoby przefiltrować klucze, tj . bananas = filter(lambda fruit: fruit.name=='banana', fruits)Lub bananas = [fruit for fruit in fruits if fruit.name=='banana']. Jest to jeden ze sposobów, dzięki którym zagnieżdżone dykty są potencjalnie bardziej wydajne; wszystko sprowadza się do sposobu, w jaki planujesz wykorzystać dane.
senderle
czy dodanie kolejnego klucza do nazwanej krotki nie ułatwi sprawy? Powiedziałbym, że dodaj nowy atrybutcount
openrijal
18

Najlepszym rozwiązaniem będzie utworzenie prostej struktury danych w celu zamodelowania tego, co masz. Następnie możesz przechowywać te obiekty na prostej liście i sortować / pobierać je w dowolny sposób.

W tym przypadku użyłbym następującej klasy:

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)

Następnie możesz po prostu skonstruować instancje „Owoce” i dodać je do listy, jak pokazano w następujący sposób:

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

Prosta lista fruitsbędzie znacznie łatwiejsza, mniej zagmatwana i lepiej utrzymana.

Kilka przykładów użycia:

Wszystkie poniższe dane wyjściowe to wynik po uruchomieniu podanego fragmentu kodu, po którym następuje:

for fruit in fruits:
    print fruit

Niesortowana lista:

Wyświetlacze:

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

Posortowane alfabetycznie według nazwy:

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

Wyświetlacze:

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

Posortowane według ilości:

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

Wyświetlacze:

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

Gdzie kolor == czerwony:

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

Wyświetlacze:

Name: apple, Color: red, Quantity: 12
Cuga
źródło
17

Baza danych, dict of dicts, słownik listy słowników, nazwana krotka (to podklasa), sqlite, redundancy ... Nie wierzyłem własnym oczom. Co jeszcze ?

„Może się zdarzyć, że słowniki z kluczami krotek nie są właściwym sposobem radzenia sobie z tą sytuacją”.

„Mam przeczucie, że baza danych jest przesadzona na potrzeby PO”;

Tak! myślałem

Tak więc, moim zdaniem, wystarczy lista krotek:

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

wynik

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
źródło
1
Cześć, podoba mi się Twoje rozwiązanie, ale nie rozwiązuje ono problemów związanych ze złożonością operacji. wszystkie typy wyszukiwania to liniowe (O (n)) w rozmiarze listy. podczas gdy Miałoby sens, że OP chciałby, aby niektóre działania były szybsze niż inne (np. uzyskanie liczby żółtych bananów byłoby czymś, czego spodziewałbym się, że będzie możliwe w O (1).
epeleg
13

Słownik prawdopodobnie nie jest tym, czego powinieneś używać w tym przypadku. Bardziej funkcjonalna biblioteka byłaby lepszą alternatywą. Prawdopodobnie prawdziwa baza danych. Najłatwiejszy byłby sqlite . Możesz zachować całość w pamięci, przekazując ciąg ': memory:' zamiast nazwy pliku.

Jeśli chcesz kontynuować tę ścieżkę, możesz to zrobić z dodatkowymi atrybutami w kluczu lub wartości. Jednak słownik nie może być kluczem do innego słownika, ale krotka może. Dokumenty wyjaśniają, co jest dozwolone. Musi to być niezmienny obiekt, który zawiera łańcuchy, liczby i krotki, które zawierają tylko łańcuchy i liczby (i więcej krotek zawierających tylko te typy rekurencyjnie ...).

Możesz zrobić swój pierwszy przykład d = {('apple', 'red') : 4}, ale bardzo trudno będzie zapytać o to, czego chcesz. Musisz zrobić coś takiego:

#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
źródło
4
Nie głosowałem i nie chciałbym przegłosować tej odpowiedzi, ponieważ w większej skali bazy danych są (oczywiście!) Najlepszą drogą. Ale mam przeczucie, że baza danych jest przesadna dla potrzeb PO; być może to wyjaśnia głos przeciw?
senderle
4

W przypadku kluczy jako krotek po prostu filtrujesz klucze z podanym drugim składnikiem i sortujesz go:

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

Sortowanie działa, ponieważ krotki mają naturalną kolejność, jeśli ich składniki mają naturalną kolejność.

Gdy klucze są raczej pełnoprawnymi obiektami, po prostu filtrujesz je według k.color == 'blue'.

Tak naprawdę nie możesz używać dykt jako kluczy, ale możesz utworzyć najprostszą klasę class Foo(object): passi dodawać do niej dowolne atrybuty w locie:

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

Te instancje mogą służyć jako klucze dyktowania, ale uważaj na ich zmienność!

9000
źródło
3

Możesz mieć słownik, w którym wpisy są listą innych słowników:

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

Wynik:

{'banan': [{'żółty': 24}], 'jabłko': [{'czerwony': 12}, {'zielony': 14}]}

Edycja: jak wskazał eumiro, możesz użyć słownika słowników:

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

Wynik:

{'banan': {'żółty': 24}, 'jabłko': {'zielony': 14, 'czerwony': 12}}

GreenMatt
źródło
2
Słownik listy słowników? Może wystarczyłby słownik słownika?
eumiro
@eumiro: Dzięki, masz rację i to był mój oryginalny pomysł. Jednak przekształciłem go w dyktowanie list dyktatów podczas kodowania oryginalnego przykładu. Dodałem przykład dyktowania.
GreenMatt
Zagnieżdżone słowniki wydają się być mylące. Proszę zobaczyć moją odpowiedź
Cuga
@Cuga: Zgadzam się, że dyktaty itp. Mogą być mylące. Podam tylko ilustracyjny przykład, aby odpowiedzieć na zadane pytanie @ Nico.
GreenMatt
Przepraszam: nie chciałem sugerować, że twoje rozwiązanie jest złe; najwyraźniej działa, aw niektórych sytuacjach może być idealny. Chciałem podzielić się moim spojrzeniem na tę sytuację.
Cuga
2

Ten typ danych jest efektywnie pobierany ze struktury danych podobnej do Trie. Pozwala również na szybkie sortowanie. Wydajność pamięci może jednak nie być tak duża.

Tradycyjna trie przechowuje każdą literę słowa jako węzeł w drzewie. Ale w twoim przypadku twój „alfabet” jest inny. Przechowujesz łańcuchy zamiast znaków.

może wyglądać mniej więcej tak:

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

zobacz ten link: trie in python

Scott Morken
źródło
2

Chcesz używać dwóch klawiszy niezależnie, więc masz dwie możliwości:

  1. Przechowuj dane redundantnie za pomocą dwóch dykt jako {'banana' : {'blue' : 4, ...}, .... }i {'blue': {'banana':4, ...} ...}. Wtedy wyszukiwanie i sortowanie jest łatwe, ale musisz upewnić się, że zmodyfikujesz dykty razem.

  2. Przechowuj to tylko jeden dykt, a następnie napisz funkcje, które będą po nich iterować, np .:

    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()]
wysoka przepustowość
źródło
Nie mogę zrozumieć, dlaczego kod w mojej odpowiedzi nie pojawia się we właściwym formacie. Próbowałem edytować i oznaczyć ostatnie dwie linie jako kod, ale to nie działa!
highBandWidth
1
utworzyłeś listę numerowaną, a parser interpretuje kod (wcięte 4 spacje) jako kontynuację drugiej pozycji tej listy. Wcięcie kodu o kolejne 4 spacje, w sumie 8, a parser rozpozna kod jako kod i sformatuje go poprawnie.
senderle