Python: Список дикту, якщо існує приріст значення dict, якщо він не додає новий


107

Я хотів би зробити щось подібне.

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

Як я можу зробити ? Я не знаю, чи варто брати кортеж, щоб відредагувати його чи розібрати кордонні індекси?

Будь-яка допомога?

Відповіді:


207

Це дуже дивний спосіб організації речей. Якщо ви зберігаєте в словнику, це легко:

# 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

Цей код для оновлення словника лічильників є загальною "схемою" в Python. Це так часто, що існує спеціальна структура даних defaultdict, створена саме для того, щоб зробити це ще простіше:

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

Якщо ви звертаєтесь за defaultdictдопомогою ключа, а ключ ще не знаходиться в defaultdict, ключ автоматично додається зі значенням за замовчуванням. Гравець defaultdictприймає виклик, який ви передали, і викликає його, щоб отримати значення за замовчуванням. У цьому випадку ми пройшли у класі int; коли Python викликає, int()він повертає нульове значення. Отже, при першому посиланні на URL-адресу його кількість ініціалізується до нуля, а потім ви додаєте його до числа.

Але словник, повний підрахунків, також є загальною схемою, тому Python забезпечує готовий до використання клас: containers.Counter Ви просто створюєте Counterекземпляр, викликаючи клас, передаючи будь-який ітерабельний; він будує словник, де ключі - це значення з ітерабельного, а значення - це кількість, скільки разів ключ з'явився в ітерабелі. Наведений вище приклад стає:

from collections import Counter  # available in Python 2.7 and newer

urls_d = Counter(list_of_urls)

Якщо вам дійсно потрібно зробити це так, як ви показали, найпростішим і швидким способом було б скористатися будь-яким із цих трьох прикладів, а потім побудувати той, який вам потрібен.

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

Якщо ви використовуєте Python 2.7 або новішу версію, ви можете це зробити в одноланковому вкладиші:

from collections import Counter

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

Мені подобається це надсилати до шаблону django, щоб я міг зробити: `{% for u in urls%} {{u.url}}: {{u.nbr}} {% endfor%}
Natim

3
Ви все ще можете зробити {% для url, nbr в urls.items%} {{url}}: {{nbr}} {% endfor%}
stefanw

160

Використання за замовчуванням працює, але це так:

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

використовуючи .get, ви можете отримати повернення за замовчуванням, якщо воно не існує. За замовчуванням це None, але у випадку, коли я вас надіслав, це було б 0.


12
Насправді я вважаю, що це найкраща відповідь, оскільки це агностик у даному словнику, який є величезним бонусом.
Bouncner

Це приємне чисте рішення.
Ділан Хогг

1
Це має бути відповіддю. Ефективний, чистий і до речі !! Я сподіваюся, що stackoverflow дозволяє громаді вирішувати відповідь разом із плакатом запитань.
mowienay

Дуже подобається, що ця відповідь просто не працює, якщо ключ None ^^ Або добре ... Потрібно ще кілька кроків ...
Седрік


17

Це завжди добре працює для мене:

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

3

Зробити це саме так? Ви можете використовувати структуру для ... 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))

Але це зовсім неелегантно. Ви справді повинні зберігати відвідані URL-адреси як СПИСОК? Якщо ви сортуєте це як dict, індексований рядком url, наприклад, це було б чистіше:

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)

У цьому другому прикладі слід зазначити кілька речей:

  • дивіться, як використання диктату для urlsусунення необхідності пройти весь urlsсписок під час тестування одного синглу url. Цей підхід буде швидшим.
  • Використання dict( )замість дужок скоротить ваш код
  • з використанням list_of_urls, urlsа urlтакож імена змінних зробити код досить важко розібрати. Краще знайти щось більш чітке, наприклад urls_to_visit, urls_already_visitedта current_url. Я знаю, це довше. Але це зрозуміліше.

І звичайно я припускаю, що dict(url='http://www.google.fr', nbr=1)це спрощення вашої власної структури даних, бо в іншому випадку urlsпросто може бути:

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

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

Що може стати дуже елегантним із позицією за замовчуванням :

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

Друга версія хороша тим, що після конвертації дикту я можу конвертувати.
Natim

3

За винятком першого разу, кожного разу, коли видно слово, тест оператора if не вдається. Якщо ви рахуєте велику кількість слів, багато, ймовірно, трапляться багато разів. У ситуації, коли ініціалізація значення відбудеться лише один раз, і збільшення цього значення відбудеться багато разів, дешевше використовувати спробу оператора:

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

ви можете прочитати більше про це: https://wiki.python.org/moin/PythonSpeed/PerformanceTips

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.