Python: Daftar dict, jika ada, tambahkan nilai dict, jika tidak tambahkan dict baru

107

Saya ingin melakukan sesuatu seperti itu.

list_of_urls = ['http://www.google.fr/', 'http://www.google.fr/', 
                'http://www.google.cn/', 'http://www.google.com/', 
                'http://www.google.fr/', 'http://www.google.fr/', 
                'http://www.google.fr/', 'http://www.google.com/', 
                'http://www.google.fr/', 'http://www.google.com/', 
                'http://www.google.cn/']

urls = [{'url': 'http://www.google.fr/', 'nbr': 1}]

for url in list_of_urls:
    if url in [f['url'] for f in urls]:
         urls[??]['nbr'] += 1
    else:
         urls.append({'url': url, 'nbr': 1})

Bagaimana saya bisa melakukannya? Saya tidak tahu apakah saya harus menggunakan tupel untuk mengeditnya atau mencari tahu indeks tupel?

Ada bantuan?

Natim
sumber

Jawaban:

207

Itu cara yang sangat aneh untuk mengatur sesuatu. Jika Anda menyimpan dalam kamus, ini mudah:

# This example should work in any version of Python.
# urls_d will contain URL keys, with counts as values, like: {'http://www.google.fr/' : 1 }
urls_d = {}
for url in list_of_urls:
    if not url in urls_d:
        urls_d[url] = 1
    else:
        urls_d[url] += 1

Kode untuk memperbarui kamus hitungan ini adalah "pola" umum di Python. Sangat umum bahwa ada struktur data khusus defaultdict,, dibuat hanya untuk membuatnya lebih mudah:

from collections import defaultdict  # available in Python 2.5 and newer

urls_d = defaultdict(int)
for url in list_of_urls:
    urls_d[url] += 1

Jika Anda mengakses defaultdictmenggunakan kunci, dan kuncinya belum ada di dalam defaultdict, kunci tersebut secara otomatis ditambahkan dengan nilai default. The defaultdictmengambil callable yang Anda berikan, dan memanggilnya untuk mendapatkan nilai default. Dalam hal ini, kami lulus di kelas int; ketika Python memanggilnya int()mengembalikan nilai nol. Jadi, pertama kali Anda mereferensikan URL, hitungannya diinisialisasi ke nol, lalu Anda menambahkan satu ke hitungan.

Tetapi kamus yang penuh hitungan juga merupakan pola umum, jadi Python menyediakan kelas yang siap digunakan: containers.Counter Anda cukup membuat sebuah Counterinstance dengan memanggil kelas tersebut, meneruskan iterable apa pun; itu membangun kamus di mana kuncinya adalah nilai dari iterable, dan nilainya dihitung dari berapa kali kunci muncul di iterable. Contoh di atas kemudian menjadi:

from collections import Counter  # available in Python 2.7 and newer

urls_d = Counter(list_of_urls)

Jika Anda benar-benar perlu melakukannya seperti yang Anda tunjukkan, cara termudah dan tercepat adalah menggunakan salah satu dari tiga contoh ini, dan kemudian membangun yang Anda butuhkan.

from collections import defaultdict  # available in Python 2.5 and newer

urls_d = defaultdict(int)
for url in list_of_urls:
    urls_d[url] += 1

urls = [{"url": key, "nbr": value} for key, value in urls_d.items()]

Jika Anda menggunakan Python 2.7 atau yang lebih baru, Anda dapat melakukannya dalam satu baris:

from collections import Counter

urls = [{"url": key, "nbr": value} for key, value in Counter(list_of_urls).items()]
steveha.dll
sumber
Saya melakukannya untuk mengirimkannya ke template django sehingga saya bisa melakukan: `{% for u in urls%} {{u.url}}: {{u.nbr}} {% endfor%}
Natim
3
Anda masih dapat melakukan {% untuk url, nbr di urls.items%} {{url}}: {{nbr}} {% endfor%}
stefanw
160

Menggunakan default berfungsi, tetapi begitu juga:

urls[url] = urls.get(url, 0) + 1

menggunakan .get, Anda bisa mendapatkan pengembalian default jika tidak ada. Secara default tidak ada, tetapi dalam kasus saya mengirim Anda, itu akan menjadi 0.

mikelikespie
sumber
12
Sebenarnya saya pikir ini adalah jawaban terbaik, karena agnostik pada kamus yang diberikan, yang merupakan bonus imo yang sangat besar.
Bouncner
Ini adalah solusi bersih yang bagus.
Dylan Hogg
1
Ini harus menjadi jawabannya. Efisien, bersih, dan to the point !! Saya berharap stackoverflow memungkinkan komunitas untuk memutuskan jawaban bersama dengan poster pertanyaan.
mowienay
Benar-benar seperti jawaban ini hanya tidak berfungsi jika kuncinya adalah Tidak Ada ^^ Atau lebih baik ... Perlu beberapa langkah lagi ...
Cedric
25

Gunakan defaultdict :

from collections import defaultdict

urls = defaultdict(int)

for url in list_of_urls:
    urls[url] += 1
Greg Hewgill
sumber
17

Ini selalu berfungsi dengan baik untuk saya:

for url in list_of_urls:
    urls.setdefault(url, 0)
    urls[url] += 1
lumut
sumber
3

Untuk melakukannya persis dengan cara Anda? Anda dapat menggunakan struktur for ... else

for url in list_of_urls:
    for url_dict in urls:
        if url_dict['url'] == url:
            url_dict['nbr'] += 1
            break
    else:
        urls.append(dict(url=url, nbr=1))

Tapi ini sangat tidak elegan. Apakah Anda benar-benar harus menyimpan url yang dikunjungi sebagai DAFTAR? Jika Anda mengurutkannya sebagai dict, diindeks oleh string url, misalnya, itu akan jauh lebih bersih:

urls = {'http://www.google.fr/': dict(url='http://www.google.fr/', nbr=1)}

for url in list_of_urls:
    if url in urls:
        urls[url]['nbr'] += 1
    else:
        urls[url] = dict(url=url, nbr=1)

Beberapa hal yang perlu diperhatikan dalam contoh kedua itu:

  • lihat bagaimana menggunakan dict untuk urlsmenghilangkan kebutuhan untuk menelusuri seluruh urlsdaftar saat menguji satu single url. Pendekatan ini akan lebih cepat.
  • Menggunakan dict( )sebagai pengganti tanda kurung membuat kode Anda lebih pendek
  • menggunakan list_of_urls, urlsdan urlsebagai nama variabel membuat kode cukup sulit untuk diurai. Lebih baik menemukan sesuatu yang lebih jelas, seperti urls_to_visit, urls_already_visiteddan current_url. Saya tahu, ini lebih lama. Tapi itu lebih jelas.

Dan tentu saja saya berasumsi itu dict(url='http://www.google.fr', nbr=1)adalah penyederhanaan struktur data Anda sendiri, karena jika tidak, urlsbisa jadi:

urls = {'http://www.google.fr':1}

for url in list_of_urls:
    if url in urls:
        urls[url] += 1
    else:
        urls[url] = 1

Yang bisa menjadi sangat elegan dengan sikap defaultdict :

urls = collections.defaultdict(int)
for url in list_of_urls:
    urls[url] += 1
Nicolas Dumazet
sumber
Versi kedua bagus karena saya dapat mengonversi dict sebagai daftar setelahnya.
Natim
3

Kecuali untuk pertama kalinya, setiap kali sebuah kata terlihat, pengujian pernyataan if gagal. Jika Anda menghitung kata dalam jumlah besar, banyak kata mungkin akan muncul beberapa kali. Dalam situasi di mana inisialisasi nilai hanya akan terjadi sekali dan augmentasi nilai tersebut akan terjadi berkali-kali, lebih murah menggunakan pernyataan percobaan:

urls_d = {}
for url in list_of_urls:
    try:
        urls_d[url] += 1
    except KeyError:
        urls_d[url] = 1

Anda dapat membaca lebih lanjut tentang ini: https://wiki.python.org/moin/PythonSpeed/PerformanceTips

pilatipus
sumber