Python: Elenco di dict, se esiste incrementa un valore di dict, in caso contrario aggiunge un nuovo dict


107

Vorrei fare qualcosa del genere.

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})

Come posso fare ? Non so se dovrei prendere la tupla per modificarla o capire gli indici della tupla?

Qualsiasi aiuto ?

Risposte:


207

Questo è un modo molto strano per organizzare le cose. Se hai memorizzato in un dizionario, questo è facile:

# 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

Questo codice per l'aggiornamento di un dizionario dei conteggi è un "modello" comune in Python. È così comune che esiste una struttura dati speciale defaultdict, creata proprio per renderlo ancora più semplice:

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

Se si accede a defaultdictutilizzando una chiave e la chiave non è già presente in defaultdict, la chiave viene automaticamente aggiunta con un valore predefinito. Il defaultdictprende il callable avete passato, e lo chiama per ottenere il valore di default. In questo caso, siamo passati in classe int; quando Python chiama int()restituisce un valore zero. Quindi, la prima volta che fai riferimento a un URL, il suo conteggio viene inizializzato a zero, quindi ne aggiungi uno al conteggio.

Ma un dizionario pieno di conteggi è anche un modello comune, quindi Python fornisce una classe pronta per l'uso: containers.Counter basta creare Counterun'istanza chiamando la classe, passando qualsiasi iterabile; crea un dizionario in cui le chiavi sono valori dell'iterabile e i valori sono i conteggi di quante volte la chiave è apparsa nell'iterabile. L'esempio sopra diventa quindi:

from collections import Counter  # available in Python 2.7 and newer

urls_d = Counter(list_of_urls)

Se hai davvero bisogno di farlo nel modo in cui hai mostrato, il modo più semplice e veloce sarebbe quello di utilizzare uno qualsiasi di questi tre esempi e quindi creare quello che ti serve.

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

Se stai usando Python 2.7 o più recente, puoi farlo in una riga:

from collections import Counter

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

Mi piace inviarlo a un template django così posso fare: `{% for u in urls%} {{u.url}}: {{u.nbr}} {% endfor%}
Natim

3
Puoi ancora fare {% for url, nbr in urls.items%} {{url}}: {{nbr}} {% endfor%}
stefanw

160

Usare l'impostazione predefinita funziona, ma lo fa anche:

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

utilizzando .get, puoi ottenere un ritorno predefinito se non esiste. Per impostazione predefinita è Nessuno, ma nel caso in cui ti ho inviato, sarebbe 0.


12
In realtà penso che questa sia la risposta migliore, poiché è agnostico sul dizionario dato, il che è un enorme vantaggio imo.
Bouncner

Questa è una bella soluzione pulita.
Dylan Hogg

1
Questa dovrebbe essere la risposta. Efficiente, pulito e al punto !! Spero che stackoverflow permetta alla comunità di decidere la risposta insieme al poster della domanda.
mowienay

Mi piace davvero questa risposta solo che non funziona se la chiave è Nessuno ^^ O beh ... Ha bisogno di altri passaggi ...
Cedric


17

Funziona sempre bene per me:

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

3

Per farlo esattamente a modo tuo? Potresti usare la struttura 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))

Ma è abbastanza inelegante. Devi davvero memorizzare gli URL visitati come ELENCO? Se lo ordinate come un dict, indicizzato dalla stringa dell'URL, ad esempio, sarebbe molto più pulito:

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)

Alcune cose da notare in quel secondo esempio:

  • vedere come l'utilizzo di un dict per urlsrimuove la necessità di passare attraverso l'intero urlselenco quando si prova per uno singolourl . Questo approccio sarà più veloce.
  • utilizzando dict( ) posto delle parentesi graffe rende il codice più breve
  • utilizzando list_of_urls, urlse urlcome nomi di variabili rendono il codice molto difficile da analizzare. E 'meglio trovare qualcosa di più chiaro, come ad esempio urls_to_visit, urls_already_visitede current_url. Lo so, è più lungo. Ma è più chiaro.

E ovviamente presumo che dict(url='http://www.google.fr', nbr=1)sia una semplificazione della tua struttura dati, perché altrimenti urlspotrebbe essere semplicemente:

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

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

Che può diventare molto elegante con la posizione di defaultdict :

urls = collections.defaultdict(int)
for url in list_of_urls:
    urls[url] += 1

La seconda versione è buona poiché posso convertire il dict come elenco dopo.
Natim

3

Tranne che per la prima volta, ogni volta che viene vista una parola il test dell'istruzione if fallisce. Se stai contando un numero elevato di parole, molte probabilmente si ripetono più volte. In una situazione in cui l'inizializzazione di un valore avverrà solo una volta e l'aumento di quel valore si verificherà molte volte, è più economico utilizzare un'istruzione try:

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

puoi leggere di più su questo: https://wiki.python.org/moin/PythonSpeed/PerformanceTips

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.