Python: Lista dict, jeśli istnieje, zwiększ wartość dict, jeśli nie, dołącz nowy dict

107

Chciałbym coś takiego zrobić.

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

Jak mam to zrobić? Nie wiem, czy powinienem wziąć krotkę, aby ją edytować, czy znaleźć indeksy krotki?

Jakaś pomoc ?

Natim
źródło

Odpowiedzi:

207

To bardzo dziwny sposób porządkowania rzeczy. Jeśli zapisałeś w słowniku, jest to łatwe:

# 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

Ten kod służący do aktualizowania słownika zliczeń jest typowym „wzorcem” w Pythonie. Jest to tak powszechne, że istnieje specjalna struktura danych defaultdict, stworzona tylko po to, aby to jeszcze ułatwić:

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

Jeśli uzyskasz dostęp defaultdictza pomocą klucza, a klucza nie ma jeszcze w defaultdict, klucz zostanie automatycznie dodany z wartością domyślną. defaultdictBierze wywoływalnym zdałeś się i wzywa go, aby uzyskać wartość domyślną. W tym przypadku zdaliśmy w klasie int; gdy wywołuje Python int(), zwraca wartość zero. Tak więc, gdy po raz pierwszy odwołujesz się do adresu URL, jego liczba jest inicjowana na zero, a następnie dodajesz jeden do liczby.

Ale słownik pełen liczników jest również powszechnym wzorcem, więc Python zapewnia gotową do użycia klasę: containers.Counter Po prostu tworzysz Counterinstancję, wywołując ją i przekazując dowolną iterowalną; tworzy słownik, w którym klucze są wartościami z elementu iterowalnego, a wartości są zliczeniami, ile razy klucz pojawił się w iterowalnym. Powyższy przykład staje się wtedy:

from collections import Counter  # available in Python 2.7 and newer

urls_d = Counter(list_of_urls)

Jeśli naprawdę musisz to zrobić w sposób, który pokazałeś, najłatwiejszym i najszybszym sposobem byłoby użycie dowolnego z tych trzech przykładów, a następnie zbudowanie tego, którego potrzebujesz.

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

Jeśli używasz Pythona 2.7 lub nowszego, możesz to zrobić w jednej linii:

from collections import Counter

urls = [{"url": key, "nbr": value} for key, value in Counter(list_of_urls).items()]
steveha
źródło
Robię to, aby wysłać go do szablonu django, abym mógł zrobić: `{% for u in urls%} {{u.url}}: {{u.nbr}} {% endfor%}
Natim
3
Nadal możesz zrobić {% dla url, nbr w urls.items%} {{url}}: {{nbr}} {% endfor%}
stefanw
161

Używanie wartości domyślnych działa, ale tak samo jest:

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

używając .get, możesz uzyskać domyślny zwrot, jeśli nie istnieje. Domyślnie jest to Brak, ale w przypadku, gdy Ci wysłałem, będzie to 0.

mikelikespie
źródło
13
Właściwie myślę, że to najlepsza odpowiedź, ponieważ jest agnostykiem w danym słowniku, co jest ogromnym bonusem imo.
Bouncner
To ładne, czyste rozwiązanie.
Dylan Hogg,
2
To powinna być odpowiedź. Sprawnie, czysto i na temat !! Mam nadzieję, że stackoverflow pozwoli społeczności zdecydować o odpowiedzi wraz z plakatem z pytaniem.
mowienay
Naprawdę podoba mi się ta odpowiedź, po prostu nie działa, jeśli klucz to Brak ^^ Albo ... Potrzebuje więcej kroków ...
Cedric
25

Użyj defaultdict :

from collections import defaultdict

urls = defaultdict(int)

for url in list_of_urls:
    urls[url] += 1
Greg Hewgill
źródło
parafraza rozwiązania
mikelikespiego
17

To zawsze działa dobrze dla mnie:

for url in list_of_urls:
    urls.setdefault(url, 0)
    urls[url] += 1
mossplix
źródło
parafraza rozwiązania
mikelikespiego
3

Zrobić to dokładnie po swojemu? Możesz użyć struktury 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))

Ale jest to dość nieeleganckie. Czy naprawdę musisz przechowywać odwiedzane adresy URL jako LISTĘ? Jeśli posortujesz go jako dykt, zindeksowany na przykład przez ciąg adresu URL, będzie to o wiele czystsze:

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)

Kilka rzeczy, na które należy zwrócić uwagę w drugim przykładzie:

  • zobacz, jak użycie dyktowania urlseliminuje potrzebę przechodzenia przez całą urlslistę podczas testowania pojedynczegourl . To podejście będzie szybsze.
  • Użycie dict( )zamiast nawiasów klamrowych skraca kod
  • za pomocą list_of_urls, urlsiurl jako nazwy zmiennych, aby kod dość trudne do analizowania. Lepiej znaleźć coś jaśniejszego, takich jak urls_to_visit, urls_already_visitedi current_url. Wiem, to dłużej. Ale to jest jaśniejsze.

I oczywiście to zakładam dict(url='http://www.google.fr', nbr=1) jest to uproszczenie własnej struktury danych, ponieważ w przeciwnym razie urlsmogłoby to być po prostu:

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

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

Co może stać się bardzo eleganckie przy domyślnym stanowisku:

urls = collections.defaultdict(int)
for url in list_of_urls:
    urls[url] += 1
Nicolas Dumazet
źródło
Druga wersja jest dobra, ponieważ mogę przekonwertować dyktę na listę po.
Natim
3

Z wyjątkiem pierwszego razu, za każdym razem, gdy widzimy słowo, test instrukcji if kończy się niepowodzeniem. Jeśli liczysz dużą liczbę słów, wiele z nich prawdopodobnie wystąpi wielokrotnie. W sytuacji, gdy inicjalizacja wartości ma nastąpić tylko raz, a zwiększenie tej wartości nastąpi wielokrotnie, tańsze jest użycie instrukcji try:

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

możesz przeczytać więcej na ten temat: https://wiki.python.org/moin/PythonSpeed/PerformanceTips

pilatipus
źródło