2011-02-17 6 views
215

¿Hay alguna manera de tener un defaultdict(defaultdict(int)) para hacer que el siguiente código funcione?Python: defaultdict of defaultdict?

for x in stuff: 
    d[x.a][x.b] += x.c_int 

d tiene que ser construido ad hoc, dependiendo de x.a y x.b elementos.

podría utilizar:

for x in stuff: 
    d[x.a,x.b] += x.c_int 

pero entonces yo no sería capaz de usar:

d.keys() 
d[x.a].keys() 
+3

Ver pregunta similar [_¿Cuál es la mejor forma de implementar diccionarios anidados en Python? _] (Http://stackoverflow.com/questions/635483/what-is-the-best-way-to-implement-nested- diccionarios-en-python). También hay información posiblemente útil en el artículo de Wikipedia sobre [_Autovivification_] (https://en.wikipedia.org/wiki/Autovivification#Python). – martineau

Respuesta

383

Sí así:

defaultdict(lambda: defaultdict(int)) 

El argumento de un defaultdict (en este caso es lambda: defaultdict(int)) se invocará cuando intente acceder a una clave que no existe. El valor de retorno se establecerá como el nuevo valor de esta clave, lo que significa que en nuestro caso el valor de d[Key_doesnt_exist] será defaultdict(int).

Si intenta acceder a una clave desde este último valor predeterminado, es decir, d[Key_doesnt_exist][Key_doesnt_exist], devolverá 0, que es el valor de retorno del argumento del último incumplimiento, por ejemplo, int().

+6

¡funciona genial! ¿podrías explicar el razonamiento detrás de esta sintaxis? – Jonathan

+31

@Jonathan: Sí, seguro, se invocará el argumento de un 'defaultdict' (en este caso es' lambda: defaultdict (int) ') cuando intente acceder a una clave que no existe y el valor de retorno de la misma se establecerá como el nuevo valor de esta clave, lo que significa que en nuestro caso el valor de 'd [Key_dont_exist]' será 'defaultdict (int)', y si intenta acceder a una clave desde este último defaultdig es decir 'd [Key_dont_exist] [Key_dont_exist] 'devolverá 0, que es el valor de retorno del argumento del último' 'defaultdict'', es decir,' int() ', Espero que haya sido útil. – mouad

+22

El argumento para 'defaultdict' debe ser una función. 'defaultdict (int)' es un diccionario, mientras que 'lambda: defaultdict (int)' es una función que devuelve un diccionario. – has2k1

40

El parámetro para el constructor de defaultdict es la función que será llamada para construir nuevos elementos. ¡Entonces usemos una lambda!

>>> from collections import defaultdict 
>>> d = defaultdict(lambda : defaultdict(int)) 
>>> print d[0] 
defaultdict(<type 'int'>, {}) 
>>> print d[0]["x"] 
0 

Como Python 2.7, hay una even better solution using Counter:

>>> from collections import Counter 
>>> c = Counter() 
>>> c["goodbye"]+=1 
>>> c["and thank you"]=42 
>>> c["for the fish"]-=5 
>>> c 
Counter({'and thank you': 42, 'goodbye': 1, 'for the fish': -5}) 

Algunas características de la prima

>>> c.most_common()[:2] 
[('and thank you', 42), ('goodbye', 1)] 

Para obtener más información, véase PyMOTW - Collections - Container data types y Python Documentation - collections

+4

Solo para completar el círculo aquí, querrá usar 'd = defaultdict (lambda: Counter())' en lugar de 'd = defaultdict (lambda: defaultdict (int))' para tratar específicamente el problema como se planteó originalmente. – gumption

+1

@gumption puede usar 'd = defaultdict (Counter())' no necesita una lambda en este caso – Deb

23

Me resulta un poco más elegante de use partial :

import functools 
dd_int = functools.partial(defaultdict, int) 
defaultdict(dd_int) 

Por supuesto, esto es lo mismo que un lambda.

6

Otros han respondido correctamente a su pregunta de cómo conseguir lo siguiente para el trabajo:

for x in stuff: 
    d[x.a][x.b] += x.c_int 

Una alternativa sería utilizar tuplas para las llaves:

d = defaultdict(int) 
for x in stuff: 
    d[x.a,x.b] += x.c_int 
    # ^^^^^^^ tuple key 

Lo bueno de este enfoque es que es simple y puede expandirse fácilmente. Si necesita un mapeo de tres niveles de profundidad, solo use una tupla de tres elementos para la clave.

+4

Esta solución significa que no es fácil obtener todo d [xa], ya que necesita introspectar cada tecla para ver si tiene xa como el primer elemento de la tupla. –

+5

Si desea anidar 3 niveles de profundidad, entonces simplemente defínalo como 3 niveles: d = defaultdict (lambda: defaultdict (lambda: defaultdict (int))) –

Cuestiones relacionadas