2009-11-07 6 views
60

Je voudrais faire quelque chose comme ça.Python: Liste de dict, si existe incrémenter une valeur dict, sinon ajouter une nouvelle dict

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 l'éditer ou comprendre l'indice de tuple?

Une aide?

+0

Les réponses sont très intéressantes, merci. – Natim

Répondre

124

C'est une façon très étrange d'organiser les choses. Si vous avez enregistré 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 pour mettre à jour un dictionnaire de comptes est un « modèle » commun en Python. Il est si fréquent qu'il y ait une structure de données spéciale, defaultdict, créé juste pour faire 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 la clé est pas déjà dans la defaultdict, la clé est automatiquement ajouté avec une valeur par défaut. Le defaultdict prend l'appelable que vous avez passé, et l'appelle pour obtenir la valeur par défaut. Dans ce cas, nous avons passé dans la classe int; lorsque Python 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 compte.

Mais un dictionnaire complet de comptes est également un modèle commun, donc Python fournit une classe prête à l'emploi: containers.Counter Vous venez de créer une instance Counter 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ératif, et les valeurs sont des comptes de combien de fois la clé est apparue dans le 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 de la façon dont vous avez fait preuve, la façon la plus simple et la plus rapide serait d'utiliser l'un de ces trois exemples, puis construire 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écente, vous pouvez le faire dans une seule ligne:

from collections import Counter 

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

Je fais comme ça pour l'envoyer à un template django donc je peux faire: '{% pour vous dans urls%} {{u.url}}: {{u.nbr}} {% endfor%} – Natim

+3

Vous pouvez toujours faire {% pour url, nbr dans urls.items%} {{url}}: {{nbr}} {% endfor%} – stefanw

+0

Ok sonne bien :) Merci – Natim

2

Pour le faire exactement comme vous le souhaitez? Vous pouvez utiliser la pour ... la structure autre

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 il est tout à fait inélégant. Avez-vous vraiment besoin de stocker les URL visitées dans un LISTE? Si vous triez comme un dict, indexé par la chaîne url, par exemple, il serait 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 choses à noter dans ce second exemple:

  • voir comment l'aide d'un dict pour urls supprime le besoin de parcourir toute la liste urls lors du test d'un seul url. Cette approche sera plus rapide.
  • Utiliser dict() au lieu d'accolades rend votre code plus court
  • utilisant list_of_urls, urls et url comme noms de variables font le code assez difficile à analyser. Il est préférable de trouver quelque chose de plus clair, comme urls_to_visit, urls_already_visited et 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) est une simplification de votre propre structure de données, car sinon, urls pourrait simplement:

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 être très élégante avec la position defaultdict:

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

La deuxième version est bonne car je peux convertir le dict en une liste après. – Natim

16

Utilisation defaultdict:

from collections import defaultdict 

urls = defaultdict(int) 

for url in list_of_urls: 
    urls[url] += 1 
81

Utilisation des travaux par défaut, mais il en va :

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, il n'y en a pas, mais dans le cas que je vous ai envoyé, il serait 0.

+5

En fait, je pense que c'est la meilleure réponse, car il est agnostique sur le dictionnaire donné, ce qui est un énorme bonus imo. – Bouncner

+0

Ceci est une belle solution propre. –

+0

Cela devrait être la réponse. Efficace, propre et au point !! J'espère que stackoverflow permet à la communauté de décider de la réponse avec l'affiche de la question. – mowienay

3

cela fonctionne toujours bien pour moi ...

 

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


 
2

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 va se produire une fois 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 lire plus: https://wiki.python.org/moin/PythonSpeed/PerformanceTips

Questions connexes