Filter dict hanya berisi kunci tertentu?

496

Saya punya dictyang memiliki banyak entri. Saya hanya tertarik pada beberapa dari mereka. Apakah ada cara mudah untuk memangkas yang lainnya?

Mpen
sumber
Sangat membantu untuk mengatakan apa jenis kunci (bilangan bulat? String? Tanggal? Objek sewenang-wenang?) Dan dengan demikian apakah ada tes sederhana (string, regex, daftar keanggotaan, atau ketimpangan numerik) untuk memeriksa kunci mana yang masuk atau keluar. Atau kita perlu memanggil fungsi arbitrer untuk menentukan itu.
smci
@smci Kunci string. Jangan pernah berpikir bahwa saya bisa menggunakan yang lain; Saya sudah mengkode dalam JS dan PHP begitu lama ...
mpen

Jawaban:

656

Membangun dikte baru:

dict_you_want = { your_key: old_dict[your_key] for your_key in your_keys }

Menggunakan pemahaman kamus.

Jika Anda menggunakan versi yang tidak memilikinya (yaitu Python 2.6 dan sebelumnya), buatlah dict((your_key, old_dict[your_key]) for ...). Itu sama, meskipun lebih jelek.

Perhatikan bahwa ini, tidak seperti versi jnnnnn, memiliki kinerja yang stabil (hanya bergantung pada jumlah tombol_Anda) untuk old_dictukuran berapa saja. Baik dari segi kecepatan maupun memori. Karena ini adalah ekspresi generator, ia memproses satu item pada satu waktu, dan itu tidak melihat semua item old_dict.

Menghapus semua yang ada di tempat:

unwanted = set(keys) - set(your_dict)
for unwanted_key in unwanted: del your_dict[unwanted_key]
Komunitas
sumber
8
"Menggunakan pemahaman kamus, jika Anda menggunakan versi yang tidak memilikinya" == version <= 2.6
getekha
8
Melempar KeyError jika salah satu kunci filer tidak ada di old_dict. Saya akan menyarankan {k: d [k] untuk k dalam filter jika k dalam d}
Peter Gibson
1
@ PeterGibson Ya, jika itu bagian dari persyaratan, Anda perlu melakukan sesuatu tentang itu. Apakah itu menjatuhkan kunci secara diam-diam, menambahkan nilai default, atau sesuatu yang lain, tergantung pada apa yang Anda lakukan; ada banyak kasus penggunaan di mana pendekatan Anda salah. Ada juga banyak di mana kunci yang hilang old_dictmengindikasikan bug di tempat lain, dan dalam hal ini saya sangat suka kesalahan untuk hasil diam-diam salah.
@delnan, juga tambahan "if k in d" memperlambat Anda jika d besar, saya hanya berpikir itu layak disebut
Peter Gibson
7
@PeterGibson Tidak, pencarian kamus adalah O (1).
130

Pemahaman dikt sedikit lebih elegan:

foodict = {k: v for k, v in mydict.items() if k.startswith('foo')}
ransford
sumber
Terpilih. Saya sedang berpikir tentang menambahkan jawaban yang mirip dengan ini. Hanya karena penasaran, mengapa {k: v untuk k, v di dict.items () ...} daripada {k: dict [k] untuk k di dict ...} Apakah ada perbedaan kinerja?
Hart Simha
4
Menjawab pertanyaan saya sendiri. {K: dict [k] untuk k di dict ...} sekitar 20-25% lebih cepat, setidaknya dalam Python 2.7.6, dengan kamus berisi 26 item (timeit (..., setup = "d = {chr (x + 97): x +1 untuk x dalam rentang (26)} ")), tergantung pada berapa banyak item yang disaring (menyaring kunci konsonan lebih cepat daripada menyaring kunci vokal karena Anda mencari lebih sedikit item). Perbedaan dalam kinerja mungkin menjadi kurang signifikan ketika ukuran kamus Anda bertambah.
Hart Simha
5
Mungkin perf yang sama jika Anda menggunakannya mydict.iteritems(). .items()membuat daftar lain.
Pat
64

Berikut ini contoh dalam python 2.6:

>>> a = {1:1, 2:2, 3:3}
>>> dict((key,value) for key, value in a.iteritems() if key == 1)
{1: 1}

Bagian penyaringan adalah ifpernyataan.

Metode ini lebih lambat daripada jawaban delnan jika Anda hanya ingin memilih beberapa kunci yang sangat banyak.

jnnnnn
sumber
11
kecuali saya mungkin akan menggunakan if key in ('x','y','z')kurasa.
mpen
jika Anda sudah tahu kunci mana yang Anda inginkan, gunakan jawaban delnan. Jika Anda perlu menguji setiap kunci dengan pernyataan if, gunakan jawaban ransford.
jnnnnn
1
Solusi ini memiliki satu keunggulan lagi. Jika kamus dikembalikan dari panggilan fungsi yang mahal (yaitu a / old_dict adalah panggilan fungsi) solusi ini memanggil fungsi hanya sekali. Dalam lingkungan imperatif menyimpan kamus yang dikembalikan oleh fungsi dalam variabel bukan masalah besar tetapi dalam lingkungan fungsional (misalnya dalam lambda) ini adalah pengamatan utama.
gae123
21

Anda dapat melakukannya dengan proyek fungsi dari saya funcy perpustakaan:

from funcy import project
small_dict = project(big_dict, keys)

Lihat juga pada select_keys .

Suor
sumber
20

Kode 1:

dict = { key: key * 10 for key in range(0, 100) }
d1 = {}
for key, value in dict.items():
    if key % 2 == 0:
        d1[key] = value

Kode 2:

dict = { key: key * 10 for key in range(0, 100) }
d2 = {key: value for key, value in dict.items() if key % 2 == 0}

Kode 3:

dict = { key: key * 10 for key in range(0, 100) }
d3 = { key: dict[key] for key in dict.keys() if key % 2 == 0}

Semua bagian dari kinerja kode diukur dengan timeit menggunakan angka = 1000, dan dikumpulkan 1000 kali untuk setiap bagian kode.

masukkan deskripsi gambar di sini

Untuk python 3.6 kinerja tiga cara kunci dict filter hampir sama. Untuk python 2.7 kode 3 sedikit lebih cepat.

Y Y
sumber
hanya ingin tahu, apakah Anda membuat plot itu dari Python?
user5359531
1
ggplot2 di R - bagian dari tidyverse
keithpjolley
18

Lambda satu liner ini harus bekerja:

dictfilt = lambda x, y: dict([ (i,x[i]) for i in x if i in set(y) ])

Ini sebuah contoh:

my_dict = {"a":1,"b":2,"c":3,"d":4}
wanted_keys = ("c","d")

# run it
In [10]: dictfilt(my_dict, wanted_keys)
Out[10]: {'c': 3, 'd': 4}

Ini adalah pemahaman daftar dasar yang berulang pada kunci dikte Anda (i in x) dan menampilkan daftar pasangan tuple (kunci, nilai) jika kunci tersebut tinggal dalam daftar kunci yang Anda inginkan (y). Dict () membungkus semuanya menjadi output sebagai objek dict.

Jim
sumber
Harus menggunakan setuntuk wanted_keys, tetapi sebaliknya terlihat bagus.
mpen
Ini memberi saya kamus kosong jika kamus asli saya berisi daftar menggantikan nilai. Ada solusi?
FaCoffee
@ Francesco, dapatkah Anda memberikan contoh? Jika saya menjalankan dictfilt({'x':['wefwef',52],'y':['iuefiuef','efefij'],'z':['oiejf','iejf']}, ('x','z')):, ia kembali {'x': ['wefwef', 52], 'z': ['oiejf', 'iejf']}seperti yang dimaksudkan.
Jim
Saya mencoba ini dengan: dict={'0':[1,3], '1':[0,2,4], '2':[1,4]}dan hasilnya adalah {}, yang saya anggap sebagai dict kosong.
FaCoffee
Satu hal, "dict" adalah kata yang dilindungi undang-undang sehingga Anda tidak boleh menggunakannya untuk memberi nama dict. Apa kunci yang ingin Anda tarik? Jika saya menjalankan:, foo = {'0':[1,3], '1':[0,2,4], '2':[1,4]}; dictfilt(foo,('0','2'))saya mendapatkan: {'0': [1, 3], '2': [1, 4]}yang merupakan hasil yang diinginkan
Jim
14

Diberikan kamus asli Anda origdan sekumpulan entri yang Anda minati keys:

filtered = dict(zip(keys, [orig[k] for k in keys]))

yang tidak sebaik jawaban delnan, tetapi harus bekerja di setiap versi Python yang menarik. Namun, ini rapuh untuk setiap elemen yang keysada di kamus asli Anda.

Kai
sumber
Nah, ini pada dasarnya adalah versi "tuple generator versi" dari pemahaman dict saya. Sangat cocok memang, meskipun ekspresi generator diperkenalkan pada 2.4, musim semi 2005 - serius, apakah ada yang masih menggunakan ini?
1
Saya tidak setuju; 2.3 seharusnya tidak ada lagi. Namun, sebagai survei lama tentang penggunaan 2.3: moinmo.in/PollAboutRequiringPython24 Versi singkat: RHEL4, SLES9, dikirimkan dengan OS X 10.4
Kai
7

Berdasarkan jawaban yang diterima oleh delnan.

Bagaimana jika salah satu kunci yang Anda inginkan tidak ada di old_dict? Solusi delnan akan melempar pengecualian KeyError yang dapat Anda tangkap. Jika bukan itu yang Anda butuhkan, mungkin Anda ingin:

  1. hanya sertakan kunci yang ada di old_dict dan kumpulan want_keys Anda.

    old_dict = {'name':"Foobar", 'baz':42}
    wanted_keys = ['name', 'age']
    new_dict = {k: old_dict[k] for k in set(wanted_keys) & set(old_dict.keys())}
    
    >>> new_dict
    {'name': 'Foobar'}
  2. memiliki nilai default untuk kunci yang tidak disetel di old_dict.

    default = None
    new_dict = {k: old_dict[k] if k in old_dict else default for k in wanted_keys}
    
    >>> new_dict
    {'age': None, 'name': 'Foobar'}
MyGGaN
sumber
Anda juga bisa melakukannya{k: old_dict.get(k, default) for k in ...}
Moberg
6

Fungsi ini akan melakukan trik:

def include_keys(dictionary, keys):
    """Filters a dict by only including certain keys."""
    key_set = set(keys) & set(dictionary.keys())
    return {key: dictionary[key] for key in key_set}

Sama seperti versi delnan, kamus ini menggunakan pemahaman kamus dan memiliki kinerja yang stabil untuk kamus besar (hanya bergantung pada jumlah kunci yang Anda izinkan, dan bukan jumlah total kunci dalam kamus).

Dan seperti versi MyGGan, yang ini memungkinkan daftar kunci Anda untuk memasukkan kunci yang mungkin tidak ada dalam kamus.

Dan sebagai bonus, inilah kebalikannya, di mana Anda dapat membuat kamus dengan mengecualikan kunci tertentu di aslinya:

def exclude_keys(dictionary, keys):
    """Filters a dict by excluding certain keys."""
    key_set = set(dictionary.keys()) - set(keys)
    return {key: dictionary[key] for key in key_set}

Perhatikan bahwa tidak seperti versi delnan, operasi tidak dilakukan di tempat, sehingga kinerjanya terkait dengan jumlah kunci dalam kamus. Namun, keuntungannya adalah bahwa fungsi ini tidak akan mengubah kamus yang disediakan.

Sunting: Menambahkan fungsi terpisah untuk mengecualikan kunci tertentu dari dikt.

Ryan
sumber
Anda harus mengizinkan keysdengan segala jenis iterable, seperti set apa yang diterima.
mpen
Ah, panggilan yang bagus, terima kasih telah menunjukkan ini. Saya akan membuat pembaruan itu.
Ryan
Saya ingin tahu apakah Anda lebih baik dengan dua fungsi. Jika Anda bertanya kepada 10 orang "apakah invertmenyiratkan bahwa keysargumen itu dipertahankan, atau bahwa keysargumen itu ditolak?", Berapa banyak dari mereka akan setuju?
skatenerd
Diperbarui. Biarkan aku tahu apa yang Anda pikirkan.
Ryan
Ini tampaknya tidak berfungsi jika dict input memiliki daftar di tempat nilai. Dalam hal ini Anda mendapatkan dict batal. Ada solusi?
FaCoffee
4

Jika kita ingin membuat kamus baru dengan kunci yang dipilih dihapus, kita dapat menggunakan pemahaman kamus
Misalnya:

d = {
'a' : 1,
'b' : 2,
'c' : 3
}
x = {key:d[key] for key in d.keys() - {'c', 'e'}} # Python 3
y = {key:d[key] for key in set(d.keys()) - {'c', 'e'}} # Python 2.*
# x is {'a': 1, 'b': 2}
# y is {'a': 1, 'b': 2}
Srivastava
sumber
Rapi. Hanya berfungsi di Python 3. Python 2 mengatakan "TypeError: jenis operan yang tidak didukung untuk -: 'daftar' dan 'set'"
mpen
Menambahkan set (d.keys ()) untuk Python 2. Ini berfungsi saat saya menjalankan.
Srivastava
2

Pilihan lain:

content = dict(k1='foo', k2='nope', k3='bar')
selection = ['k1', 'k3']
filtered = filter(lambda i: i[0] in selection, content.items())

Tapi Anda mendapatkan list(Python 2) atau iterator (Python 3) dikembalikan oleh filter(), bukan a dict.

marsl
sumber
Bungkus filtereddi dictdan Anda mendapatkan kembali kamus!
CMCDragonkai
1

Bentuk pendek:

[s.pop(k) for k in list(s.keys()) if k not in keep]

Seperti sebagian besar jawaban menyarankan untuk menjaga keringkasan kita harus membuat objek duplikat baik itu a listatau dict. Yang ini membuat membuang-buang listtetapi menghapus kunci aslinya dict.

nehem
sumber
0

Berikut adalah metode sederhana lain yang digunakan deldalam satu liner:

for key in e_keys: del your_dict[key]

e_keysadalah daftar kunci yang akan dikecualikan. Ini akan memperbarui dict Anda daripada memberi Anda yang baru.

Jika Anda ingin keluaran baru, buat salinan salinan sebelum menghapus:

new_dict = your_dict.copy()           #Making copy of dict

for key in e_keys: del new_dict[key]
Petir hitam
sumber
0

Anda dapat menggunakan python-benedict, ini adalah subclass dict.

Instalasi: pip install python-benedict

from benedict import benedict

dict_you_want = benedict(your_dict).subset(keys=['firstname', 'lastname', 'email'])

Ini open-source di GitHub: https://github.com/fabiocaccamo/python-benedict


Penafian: Saya penulis perpustakaan ini.

Fabio Caccamo
sumber