Python: Tupel / kamus sebagai kunci, pilih, urutkan

104

misalkan saya memiliki jumlah buah dengan warna berbeda, misal, 24 pisang biru, 12 apel hijau, 0 stroberi biru, dan seterusnya. Saya ingin mengaturnya dalam struktur data dengan Python yang memungkinkan pemilihan dan penyortiran yang mudah. Ide saya adalah memasukkannya ke dalam kamus dengan tupel sebagai kuncinya, misalnya,

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

atau bahkan kamus, misalnya,

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

Saya ingin mengambil daftar semua buah biru, atau pisang semua warna, misalnya, atau mengurutkan kamus ini berdasarkan nama buahnya. Adakah cara untuk melakukan ini dengan cara yang bersih?

Mungkin kamus dengan tupel sebagai kunci bukanlah cara yang tepat untuk menangani situasi ini.

Semua saran diterima!

Nico Schlömer
sumber
26
Kedengarannya seperti Anda menginginkan database ...
Adam Rosenfield
4
Anda akan lebih baik menentukan clsas untuk memodelkan data ini, daripada mencoba mengoordinasikan koleksi yang berbeda dari nilai-nilai ini
Cuga
2
@AdamRosenfield mungkin dia sedang membangunnya.
Prof. Falken
Hanya ingin menambahkan bahwa kamus tidak dapat di-hash sehingga sintaks kedua yang Anda tanyakan tidak dimungkinkan karena {'fruit': 'banana', 'color': 'blue'} yang merupakan kamus tidak dapat digunakan sebagai kunci untuk kamus lain. itu akan menyebabkan TypeError: unhashable type: 'dict'.
epeleg

Jawaban:

147

Secara pribadi, salah satu hal yang saya suka tentang python adalah kombinasi tuple-dict. Apa yang Anda miliki di sini secara efektif adalah array 2d (di mana x = nama buah dan y = warna), dan saya biasanya adalah pendukung dikt tupel untuk mengimplementasikan array 2d, setidaknya ketika sesuatu seperti numpyatau database tidak lebih sesuai . Singkatnya, saya pikir Anda memiliki pendekatan yang baik.

Perhatikan bahwa Anda tidak dapat menggunakan dicts sebagai kunci dalam sebuah dict tanpa melakukan pekerjaan ekstra, jadi itu bukan solusi yang sangat baik.

Karena itu, Anda juga harus mempertimbangkan nametuple () . Dengan cara itu Anda bisa melakukan ini:

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

Sekarang Anda dapat menggunakan dikt akun buah Anda:

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

Trik lainnya:

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

Menggema chmullig, untuk mendapatkan daftar semua warna dari satu buah, Anda harus menyaring tombolnya, yaitu

bananas = [fruit for fruit in fruits if fruit.name=='banana']
pengirim
sumber
#senderle Anda menulis sebagai komentar untuk jawaban lain "Tapi firasat saya adalah bahwa database terlalu banyak untuk kebutuhan OP;"; Jadi, Anda lebih suka membuat subkelas bernamatuple. Tapi apa lagi contoh kelas jika bukan database mikro dengan alat mereka sendiri untuk memproses datanya?
eyquem
Bisakah saya dari sublist ekstrak itu name='banana'?
Nico Schlömer
2
Seperti yang ditunjukkan oleh chmullig, Anda harus memfilter tombol, yaitu bananas = filter(lambda fruit: fruit.name=='banana', fruits)atau bananas = [fruit for fruit in fruits if fruit.name=='banana']. Ini adalah salah satu cara di mana dicts bersarang berpotensi lebih efisien; semuanya bermuara pada cara Anda berencana menggunakan data.
pengirim
tidak akan menambahkan lebih banyak kunci dalam tuple bernama membuat segalanya lebih mudah? Saya akan mengatakan menambahkan atribut barucount
openrijal
18

Pilihan terbaik Anda adalah membuat struktur data sederhana untuk memodelkan apa yang Anda miliki. Kemudian Anda dapat menyimpan objek ini dalam daftar sederhana dan mengurutkan / mengambilnya sesuka Anda.

Untuk kasus ini, saya akan menggunakan kelas berikut:

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)

Kemudian Anda cukup membuat instance "Buah" dan menambahkannya ke daftar, seperti yang ditunjukkan dengan cara berikut:

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

Daftar sederhana fruitsakan jauh lebih mudah, tidak membingungkan, dan lebih terawat.

Beberapa contoh penggunaan:

Semua keluaran di bawah ini adalah hasil setelah menjalankan cuplikan kode yang diberikan diikuti oleh:

for fruit in fruits:
    print fruit

Daftar yang tidak diurutkan:

Menampilkan:

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

Diurutkan menurut abjad berdasarkan nama:

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

Menampilkan:

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

Diurutkan berdasarkan kuantitas:

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

Menampilkan:

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

Dimana warna == merah:

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

Menampilkan:

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

Database, diktik, kamus daftar kamus, bernama tuple (itu subclass), sqlite, redundansi ... Saya tidak percaya dengan mata saya. Apa lagi ?

"Mungkin saja kamus dengan tupel sebagai kuncinya bukanlah cara yang tepat untuk menangani situasi ini."

"firasat saya adalah bahwa database terlalu banyak untuk kebutuhan OP;"

Ya! saya pikir

Jadi, menurut saya, daftar tupelnya cukup banyak:

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

hasil

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
sumber
1
Hai, Saya suka solusi Anda, namun solusi tersebut tidak mengatasi masalah kompleksitas operasi. semua jenis pencarian adalah liner (O (n)) dalam ukuran daftar. sementara itu akan masuk akal bahwa OP ingin memiliki beberapa tindakan lebih cepat dari yang lain (misalnya mendapatkan jumlah pisang kuning akan menjadi sesuatu yang saya harapkan mungkin di O (1).
epeleg
13

Kamus mungkin bukanlah yang seharusnya Anda gunakan dalam kasus ini. Perpustakaan berfitur lebih lengkap akan menjadi alternatif yang lebih baik. Mungkin database sungguhan. Yang termudah adalah sqlite . Anda dapat menyimpan semuanya dalam memori dengan memasukkan string ': memory:' alih-alih nama file.

Jika Anda ingin melanjutkan jalur ini, Anda dapat melakukannya dengan atribut tambahan di kunci atau nilainya. Bagaimanapun kamus tidak bisa menjadi kunci untuk kamus lain, tapi tuple bisa. Dokumen menjelaskan apa yang diperbolehkan. Ini harus berupa objek yang tidak dapat diubah, yang mencakup string, angka, dan tupel yang hanya berisi string dan angka (dan lebih banyak tupel yang hanya berisi tipe tersebut secara rekursif ...).

Anda dapat melakukan contoh pertama dengan d = {('apple', 'red') : 4}, tetapi akan sangat sulit untuk menanyakan apa yang Anda inginkan. Anda perlu melakukan sesuatu seperti ini:

#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.dll
sumber
4
Saya tidak, dan tidak akan, menolak jawaban ini, karena pada skala yang lebih besar database adalah (jelas!) Cara terbaik untuk pergi. Tapi firasat saya adalah bahwa database terlalu banyak untuk kebutuhan OP; mungkin itu menjelaskan downvote?
pengirim
4

Dengan kunci sebagai tupel, Anda cukup memfilter kunci dengan komponen kedua yang diberikan dan mengurutkannya:

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

Penyortiran berfungsi karena tupel memiliki susunan alami jika komponennya memiliki susunan alami.

Dengan kunci sebagai objek yang cukup lengkap, Anda cukup memfilter k.color == 'blue'.

Anda tidak dapat benar-benar menggunakan dicts sebagai kunci, tetapi Anda dapat membuat kelas paling sederhana seperti class Foo(object): passdan menambahkan atribut apa pun padanya dengan cepat:

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

Instance ini dapat berfungsi sebagai kunci dict, tetapi waspadalah terhadap mutabilitasnya!

9000
sumber
3

Anda bisa memiliki kamus yang isinya adalah daftar kamus lain:

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

Keluaran:

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

Sunting: Seperti yang ditunjukkan oleh eumiro, Anda dapat menggunakan kamus kamus:

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

Keluaran:

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

GreenMatt
sumber
2
Kamus daftar kamus? Mungkin kamus kamus sudah cukup?
eumiro
@eumiro: Terima kasih, Anda benar, dan itu adalah ide asli saya. Namun, saya mengubahnya menjadi dikt daftar penis saat mengkodekan contoh aslinya. Saya telah menambahkan contoh diktik.
GreenMatt
Kamus bertingkat cenderung membingungkan. Silakan lihat jawaban saya
Cuga
@ Cuga: Saya setuju bahwa penis dari penis, dll. Bisa membingungkan. Saya hanya memberikan contoh ilustrasi untuk menjawab pertanyaan @ Nico seperti yang ditanyakan.
GreenMatt
Saya minta maaf: Saya tidak bermaksud mengatakan bahwa solusi Anda salah; ini bekerja dengan jelas dan dalam beberapa situasi ini bisa menjadi yang ideal. Saya ingin berbagi pandangan saya tentang situasi ini.
Cuga
2

Jenis data ini ditarik secara efisien dari struktur data seperti Trie. Ini juga memungkinkan penyortiran cepat. Efisiensi memori mungkin tidak terlalu bagus.

Trie tradisional menyimpan setiap huruf dari sebuah kata sebagai simpul di pohon. Tetapi dalam kasus Anda "alfabet" Anda berbeda. Anda menyimpan string, bukan karakter.

mungkin terlihat seperti ini:

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

lihat tautan ini: trie dengan python

Scott Morken
sumber
2

Anda ingin menggunakan dua kunci secara terpisah, jadi Anda memiliki dua pilihan:

  1. Simpan data secara berlebihan dengan dua dicts sebagai {'banana' : {'blue' : 4, ...}, .... }dan {'blue': {'banana':4, ...} ...}. Kemudian, mencari dan menyortir itu mudah tetapi Anda harus memastikan Anda memodifikasi dicts bersama-sama.

  2. Simpan hanya satu perintah, lalu tulis fungsi yang mengulanginya, misalnya:

    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()]
highBandWidth
sumber
Saya tidak tahu mengapa kode di jawaban saya tidak muncul dalam format yang benar. Saya sudah mencoba mengedit dan menandai dua baris terakhir sebagai kode, tetapi tidak berhasil!
highBandWidth
1
Anda telah membuat daftar bernomor, dan parser menafsirkan kode tersebut (menjorok 4 spasi) sebagai kelanjutan dari item kedua dari daftar itu. Indentasi kode 4 spasi lagi dengan total 8, dan parser akan mengenali kode tersebut sebagai kode dan memformatnya dengan benar.
pengirim