2009-11-07 11 views
60

Me gustaría hacer algo así.Python: Lista de dict, si existe incremente un valor dict, si no agrega una nueva 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}) 

¿Cómo puedo hacer? No sé si debería tomar la tupla para editarla o descubrir el índice de tuplas.

¿Algún ayuda?

+0

Las respuestas son muy interesantes, gracias. – Natim

Respuesta

124

Esa es una forma muy extraña de organizar las cosas. Si almacenó en un diccionario, esto es fácil:

# 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 

Este código para la actualización de un diccionario de cuenta es un "patrón" común en Python. Es tan común que hay una estructura de datos especial, defaultdict, creado sólo para hacer esto aún más fácil: la clave

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 accede a la defaultdict utilizando una llave, y la llave no está ya en la defaultdict se agrega automáticamente con un valor predeterminado. El defaultdict toma el invocable que ingresó y lo llama para obtener el valor predeterminado. En este caso, pasamos en la clase int; cuando Python llama al int(), devuelve un valor cero. Por lo tanto, la primera vez que hace referencia a una URL, su recuento se inicializa a cero y luego agrega uno al recuento.

Pero un diccionario lleno de conteos también es un patrón común, por lo que Python proporciona una clase lista para usar: containers.Counter Usted acaba de crear una instancia de Counter llamando a la clase, pasando cualquier iterable; construye un diccionario donde las claves son valores de iterable, y los valores son recuentos de cuántas veces apareció la clave en el iterable. El ejemplo arriba se convierte en:

from collections import Counter # available in Python 2.7 and newer 

urls_d = Counter(list_of_urls) 

Si realmente necesita hacerlo de la forma que mostró, la forma más fácil y más rápido sería el uso de cualquiera de estos tres ejemplos, y luego construir el que necesita.

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 está utilizando Python 2.7 o posterior se puede hacer en una sola línea:

from collections import Counter 

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

Me gusta eso para enviarlo a una plantilla django, así que puedo: '{% for u in urls%} {{u.url}}: {{u.nbr}} {% endfor%} – Natim

+3

Todavía puedes do {% for url, nbr en urls.items%} {{url}}: {{nbr}} {% endfor%} – stefanw

+0

Ok suena genial :) Gracias – Natim

2

¿Para hacerlo exactamente a tu manera? Puede usar la estructura ... 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)) 

Pero es bastante poco elegante. ¿De verdad tienes que almacenar las URL visitadas como una LISTA? Si clasifica como un diccionario, indexado por cadena URL, por ejemplo, sería manera más limpia:

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) 

Un par de cosas a tener en cuenta en ese segundo ejemplo:

  • ver cómo el uso de una dict para urls elimina la necesidad de revisar toda la lista urls al probar una sola url. Este enfoque será más rápido.
  • Usando dict() en lugar de llaves hace que el código más corto
  • usando list_of_urls, urls y url como nombres de variables hacen que el código muy difícil de analizar. Es mejor encontrar algo más claro, como urls_to_visit, urls_already_visited y current_url. Lo sé, es más largo. Pero está más claro.

Y por supuesto que estoy asumiendo que dict(url='http://www.google.fr', nbr=1) es una simplificación de su estructura de datos, porque de lo contrario, podría ser simplemente urls:

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

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

que puede llegar muy elegante con la postura defaultdict:

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

La segunda versión es buena ya que puedo convertir el dict como una lista después. – Natim

16

Uso defaultdict:

from collections import defaultdict 

urls = defaultdict(int) 

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

El uso de las obras por defecto, pero también lo hace :

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

usando .get, puede obtener una devolución predeterminada si no existe. Por defecto es Ninguno, pero en el caso que le envié, sería 0.

+5

En realidad, creo que esta es la mejor respuesta, ya que es agnóstico en el diccionario dado, que es una gran ventaja. – Bouncner

+0

Esta es una buena solución limpia. –

+0

Esta debería ser la respuesta. Eficiente, limpio y al grano !! Espero que stackoverflow le permita a la comunidad decidir la respuesta junto con el póster de la pregunta. – mowienay

3

esto siempre funciona bien para mí ...

 

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


 
2

Excepto por primera vez, cada vez que una palabra se ve la prueba de la declaración if falla. Si está contando una gran cantidad de palabras, muchas probablemente se repitan varias veces. En una situación en la inicialización de un valor sólo va a aparecer una y el aumento de este valor se producirá muchas veces es más barato utilizar una instrucción try:

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

se puede leer más acerca de esto: https://wiki.python.org/moin/PythonSpeed/PerformanceTips

Cuestiones relacionadas