Python: список dict, если существует, увеличить значение dict, если не добавить новый 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 в URL-адресах%} {{u.url}}: {{u.nbr}} {% endfor%}
Натим,
3
Вы все еще можете использовать {% для url, nbr в urls.items%} {{url}}: {{nbr}} {% endfor%}
stefanw
161

Использование по умолчанию работает, но также:

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

используя .get, вы можете получить возврат по умолчанию, если он не существует. По умолчанию это None, но в случае, если я вам послал, это будет 0.

микеликеспи
источник
13
На самом деле я думаю, что это лучший ответ, поскольку он не зависит от данного словаря, что является огромным бонусом, imo.
Bouncner
Это хорошее чистое решение.
Дилан Хогг
2
Это должен быть ответ. Эффективно, чисто и по делу !! Я надеюсь, что stackoverflow позволит сообществу определить ответ вместе с плакатом с вопросом.
mowienay
На самом деле, как этот ответ, просто не работает, если ключ None ^^ Или хорошо ... Требуются еще несколько шагов ...
Седрик
25

Используйте defaultdict :

from collections import defaultdict

urls = defaultdict(int)

for url in list_of_urls:
    urls[url] += 1
Грег Хьюгилл
источник
пересказ решения
Микеликеспи
17

У меня это всегда отлично работает:

for url in list_of_urls:
    urls.setdefault(url, 0)
    urls[url] += 1
мох
источник
пересказ решения
Микеликеспи
3

Делать это именно по-своему? Вы можете использовать структуру 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))

Но это довольно неэлегантно. Вам действительно нужно хранить посещенные 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)

Несколько замечаний во втором примере:

  • посмотрите, как использование dict для 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

Что может быть очень элегантно с позой defaultdict :

urls = collections.defaultdict(int)
for url in list_of_urls:
    urls[url] += 1
Николя Дюмазе
источник
Вторая версия хороша тем, что я могу преобразовать dict в виде списка после.
Natim
3

За исключением первого раза, каждый раз, когда встречается слово, проверка оператора if не выполняется. Если вы считаете большое количество слов, многие, вероятно, будут встречаться несколько раз. В ситуации, когда инициализация значения происходит только один раз, а увеличение этого значения будет происходить во много раз, дешевле использовать оператор try:

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

пилатип
источник