Python: Liste de dict, s'il existe, incrémenter une valeur de dict, sinon ajouter un nouveau dict

107

J'aimerais faire quelque chose comme ça.

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

Comment puis-je faire ? Je ne sais pas si je devrais prendre le tuple pour le modifier ou trouver les indices de tuple?

De l'aide ?

Natim
la source

Réponses:

207

C'est une manière très étrange d'organiser les choses. Si vous avez stocké dans un dictionnaire, c'est 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

Ce code de mise à jour d'un dictionnaire de comptages est un "modèle" courant en Python. Il est si courant qu'il existe une structure de données spéciale defaultdict, créée juste pour rendre cela encore plus facile:

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

Si vous accédez au defaultdictà l' aide d'une clé et que la clé n'est pas déjà dans le defaultdict, la clé est automatiquement ajoutée avec une valeur par défaut. Le defaultdictprend l'appelable que vous avez passé et l'appelle pour obtenir la valeur par défaut. Dans ce cas, nous sommes passés en classe int; lorsque Python l'appelle, int()il renvoie une valeur nulle. Ainsi, la première fois que vous référencez une URL, son nombre est initialisé à zéro, puis vous en ajoutez un au nombre.

Mais un dictionnaire plein de décomptes est aussi un modèle courant, donc Python fournit une classe prête à l'emploi: containers.Counter vous créez simplement une Counterinstance en appelant la classe, en passant n'importe quel itérable; il construit un dictionnaire où les clés sont des valeurs de l'itérable, et les valeurs sont des décomptes du nombre de fois où la clé est apparue dans l'itérable. L'exemple ci-dessus devient alors:

from collections import Counter  # available in Python 2.7 and newer

urls_d = Counter(list_of_urls)

Si vous avez vraiment besoin de le faire comme vous l'avez montré, le moyen le plus simple et le plus rapide serait d'utiliser l'un de ces trois exemples, puis de créer celui dont vous avez besoin.

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

Si vous utilisez Python 2.7 ou plus récent, vous pouvez le faire en une seule ligne:

from collections import Counter

urls = [{"url": key, "nbr": value} for key, value in Counter(list_of_urls).items()]
Steveha
la source
J'aime ça pour l'envoyer à un modèle django afin que je puisse faire: `{% for u in urls%} {{u.url}}: {{u.nbr}} {% endfor%}
Natim
3
Vous pouvez toujours faire {% for url, nbr in urls.items%} {{url}}: {{nbr}} {% endfor%}
stefanw
161

L'utilisation de la valeur par défaut fonctionne, mais aussi:

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

en utilisant .get, vous pouvez obtenir un retour par défaut s'il n'existe pas. Par défaut, c'est Aucun, mais dans le cas où je vous ai envoyé, ce serait 0.

mikelikespie
la source
13
En fait, je pense que c'est la meilleure réponse, car elle est agnostique sur le dictionnaire donné, ce qui est un énorme bonus imo.
Bouncner
C'est une belle solution propre.
Dylan Hogg
2
Cela devrait être la réponse. Efficace, propre et au point !! J'espère que stackoverflow permettra à la communauté de décider de la réponse avec l'affiche de la question.
mowienay le
Vraiment cette réponse ne fonctionne tout simplement pas si la clé est Aucune ^^ Ou bien ... Besoin de quelques étapes supplémentaires ...
Cédric
25

Utilisez defaultdict :

from collections import defaultdict

urls = defaultdict(int)

for url in list_of_urls:
    urls[url] += 1
Greg Hewgill
la source
paraphrase de la solution de
Mikelikespie
17

Cela fonctionne toujours bien pour moi:

for url in list_of_urls:
    urls.setdefault(url, 0)
    urls[url] += 1
mossplix
la source
paraphrase de la solution de
Mikelikespie
3

Pour le faire exactement à votre façon? Vous pouvez utiliser la structure 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))

Mais c'est assez inélégant. Devez-vous vraiment stocker les URL visitées sous forme de LISTE? Si vous le triez comme un dict, indexé par chaîne d'URL, par exemple, ce serait beaucoup plus propre:

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)

Quelques points à noter dans ce deuxième exemple:

  • voir comment l'utilisation d'un dict pour urlssupprime le besoin de parcourir toute la urlsliste lors du test d'un seulurl . Cette approche sera plus rapide.
  • Utiliser dict( )au lieu d'accolades rend votre code plus court
  • à l' aide list_of_urls, urlset urlque les noms de variables font le code assez difficile à analyser. Il vaut mieux trouver quelque chose de plus clair, comme urls_to_visit, urls_already_visitedet current_url. Je sais, c'est plus long. Mais c'est plus clair.

Et bien sûr, je suppose que dict(url='http://www.google.fr', nbr=1)c'est une simplification de votre propre structure de données, car sinon, cela urlspourrait simplement être:

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

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

Ce qui peut devenir très élégant avec la position defaultdict :

urls = collections.defaultdict(int)
for url in list_of_urls:
    urls[url] += 1
Nicolas Dumazet
la source
La deuxième version est bonne car je peux convertir le dict sous forme de liste après.
Natim
3

Sauf pour la première fois, chaque fois qu'un mot est vu, le test de l'instruction if échoue. Si vous comptez un grand nombre de mots, plusieurs se produiront probablement plusieurs fois. Dans une situation où l'initialisation d'une valeur ne se produira qu'une fois et l'augmentation de cette valeur se produira plusieurs fois, il est moins coûteux d'utiliser une instruction try:

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

vous pouvez en savoir plus à ce sujet: https://wiki.python.org/moin/PythonSpeed/PerformanceTips

pilatipus
la source