Python: Lista de dict, si existe, incremente un valor de dict, si no, agregue un nuevo dict

107

Me gustaría hacer algo así.

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

Como lo puedo hacer ? No sé si debería tomar la tupla para editarla o averiguar los índices de tupla.

Alguna ayuda ?

Natim
fuente

Respuestas:

207

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 actualizar un diccionario de conteos es un "patrón" común en Python. Es tan común que existe una estructura de datos especial defaultdict, creada solo para hacer esto aún más fácil:

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 al defaultdictmediante una clave, y la clave aún no está en el defaultdict, la clave se agrega automáticamente con un valor predeterminado. El defaultdicttoma el invocable que le pasó y lo llama para obtener el valor predeterminado. En este caso, pasamos en clase int; cuando Python llama int(), devuelve un valor cero. Entonces, la primera vez que hace referencia a una URL, su recuento se inicializa en cero y luego agrega uno al recuento.

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

from collections import Counter  # available in Python 2.7 and newer

urls_d = Counter(list_of_urls)

Si realmente necesita hacerlo de la manera que mostró, la forma más fácil y rápida sería usar 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 más reciente, puede hacerlo en una sola línea:

from collections import Counter

urls = [{"url": key, "nbr": value} for key, value in Counter(list_of_urls).items()]
steveha
fuente
Me gusta eso para enviarlo a una plantilla de django para poder hacer: `{% for u in urls%} {{u.url}}: {{u.nbr}} {% endfor%}
Natim
3
Todavía puede hacer {% para url, nbr en urls.items%} {{url}}: {{nbr}} {% endfor%}
stefanw
160

Usar el predeterminado funciona, 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 de que te envié, sería 0.

mikelikespie
fuente
12
En realidad, creo que esta es la mejor respuesta, ya que es agnóstico en el diccionario dado, lo cual es una gran ventaja en mi opinión.
Bouncner
Esta es una buena solución limpia.
Dylan Hogg
1
Esta debería ser la respuesta. Eficiente, limpio y al grano !! Espero que stackoverflow permita a la comunidad decidir la respuesta junto con el póster de preguntas.
mowienay
Realmente me gusta esta respuesta simplemente no funciona si la clave es Ninguna ^^ O bueno ... Necesita algunos pasos más ...
Cedric
25

Utilice defaultdict :

from collections import defaultdict

urls = defaultdict(int)

for url in list_of_urls:
    urls[url] += 1
Greg Hewgill
fuente
17

Esto siempre funciona bien para mí:

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

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

Pero es bastante poco elegante. ¿Realmente tienes que almacenar las URL visitadas como una LISTA? Si lo ordena como un dictado, indexado por url string, por ejemplo, sería mucho más limpio:

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)

Algunas cosas a tener en cuenta en ese segundo ejemplo:

  • vea cómo el uso de un diccionario para urlselimina la necesidad de revisar toda la urlslista al probar una sola url. Este enfoque será más rápido.
  • Usar dict( )llaves en lugar de llaves hace que su código sea más corto
  • usando list_of_urls, urlsy urlcomo nombres de variable hacen que el código sea bastante difícil de analizar. Es mejor encontrar algo más claro, como urls_to_visit, urls_already_visitedy current_url. Lo sé, es más largo. Pero está más claro.

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

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

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

Lo que puede volverse muy elegante con la postura defaultdict :

urls = collections.defaultdict(int)
for url in list_of_urls:
    urls[url] += 1
Nicolas Dumazet
fuente
La segunda versión es buena ya que puedo convertir el diccionario como una lista después.
Natim
3

Excepto por la primera vez, cada vez que se ve una palabra, la prueba de la instrucción if falla. Si está contando una gran cantidad de palabras, es probable que muchas ocurran varias veces. En una situación en la que la inicialización de un valor solo ocurrirá una vez y el aumento de ese valor ocurrirá muchas veces, es más barato usar una declaración try:

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

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

pilatipus
fuente