web-dev-qa-db-fra.com

Python: defaultdict of defaultdict?

Est-il possible d'avoir une defaultdict(defaultdict(int)) afin de faire fonctionner le code suivant?

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

d doit être construit sur mesure, en fonction des éléments x.a et x.b.

Je pourrais utiliser:

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

mais alors je ne serais pas capable d'utiliser:

d.keys()
d[x.a].keys()
273
Jonathan

Oui comme ça:

defaultdict(lambda: defaultdict(int))

L'argument d'un defaultdict (dans ce cas, lambda: defaultdict(int)) sera appelé lorsque vous essayez d'accéder à une clé inexistante. La valeur de retour de celle-ci sera définie comme nouvelle valeur de cette clé, ce qui signifie dans notre cas que la valeur de d[Key_doesnt_exist] sera defaultdict(int).

Si vous essayez d'accéder à une clé de ce dernier defaultdict, c'est-à-dire d[Key_doesnt_exist][Key_doesnt_exist], elle retournera 0, ce qui correspond à la valeur de retour de l'argument du dernier defaultdict, c'est-à-dire int().

499
mouad

Le paramètre du constructeur defaultdict est la fonction qui sera appelée pour construire de nouveaux éléments. Alors utilisons un lambda!

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

Depuis Python 2.7, il existe un ne solution encore meilleure utilisant 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})

Quelques bonus

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

Pour plus d'informations, voir PyMOTW - Collections - Types de données de conteneur et Documentation Python - Collections

48
yanjost

Je trouve un peu plus élégant d'utiliser partial:

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

Bien sûr, c'est pareil qu'un lambda.

28
Katriel

Pour référence, il est possible d'implémenter une méthode générique imbriquée defaultdict par le biais de:

from collections import defaultdict
from functools import partial
from itertools import repeat


def nested_defaultdict(default_factory, depth=1):
    result = partial(defaultdict, default_factory)
    for _ in repeat(None, depth - 1):
        result = partial(defaultdict, result)
    return result()

La profondeur définit le nombre de dictionnaires imbriqués avant que le type défini dans default_factory ne soit utilisé. Par exemple:

my_dict = nested_defaultdict(list, 3)
my_dict['a']['b']['c'].append('e')
6
Campi

D'autres ont répondu correctement à votre question sur la manière de faire fonctionner ce qui suit:

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

Une alternative serait d'utiliser des n-uplets pour les clés:

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

La bonne chose à propos de cette approche est qu’elle est simple et peut être facilement étendue. Si vous avez besoin d'un mappage sur trois niveaux, utilisez simplement un tuple à trois éléments pour la clé.

6
Steven Rumbalski