Defaultdict aninhado de defaultdict

129

Existe uma maneira de fazer um defaultdict também ser o padrão para o defaultdict? (isto é, padrão padrão recursivo de nível infinito?)

Eu quero ser capaz de fazer:

x = defaultdict(...stuff...)
x[0][1][0]
{}

Então, eu posso fazer x = defaultdict(defaultdict), mas isso é apenas um segundo nível:

x[0]
{}
x[0][0]
KeyError: 0

Existem receitas que podem fazer isso. Mas isso pode ser feito simplesmente usando os argumentos normais do defaultdict?

Observe que isso está perguntando como executar um padrão default recursivo de nível infinito, portanto, é distinto do Python: defaultdict of defaultdict? , que era como executar um comando padrão de dois níveis.

Provavelmente acabarei usando o padrão de cacho , mas quando percebi que não sabia como fazer isso, fiquei interessado.

Corley Brigman
fonte
Possível duplicata do Python: defaultdict of defaultdict?
Malioboro
2
Na verdade, não ... adicionou informações à pergunta para indicar o porquê. Embora essa seja uma pergunta útil.
Corley Brigman

Respostas:

168

Para um número arbitrário de níveis:

def rec_dd():
    return defaultdict(rec_dd)

>>> x = rec_dd()
>>> x['a']['b']['c']['d']
defaultdict(<function rec_dd at 0x7f0dcef81500>, {})
>>> print json.dumps(x)
{"a": {"b": {"c": {"d": {}}}}}

Claro que você também pode fazer isso com um lambda, mas acho que as lambdas são menos legíveis. De qualquer forma, ficaria assim:

rec_dd = lambda: defaultdict(rec_dd)
Andrew Clark
fonte
1
De fato, um exemplo perfeito, obrigado. Você poderia estendê-lo ao caso, de que os dados são carregados de json no defaultdict of defaultdict?
David Belohrad
4
Uma nota. Se você estiver tentando usar esse código enquanto a decapagem lambdanão funcionará.
Viacheslav Kondratiuk
167

As outras respostas aqui explicam como criar uma defaultdictque contém "infinitamente muitas" defaultdict, mas elas falham em abordar o que eu acho que pode ter sido sua necessidade inicial, que era simplesmente ter um decreto-padrão de duas profundidades.

Você pode estar procurando:

defaultdict(lambda: defaultdict(dict))

Os motivos pelos quais você pode preferir essa construção são:

  • É mais explícito que a solução recursiva e, portanto, provavelmente mais compreensível para o leitor.
  • Isso permite que a "folha" de defaultdictalgo diferente de um dicionário, por exemplo: defaultdict(lambda: defaultdict(list))oudefaultdict(lambda: defaultdict(set))
Chris W.
fonte
3
defaultdict (lambda: defaultdict (list)) O formato correto?
Yuvaraj Loganathan
Opa, sim, o lambdaformulário está correto - porque defaultdict(something)retorna um objeto parecido com um dicionário, mas defaultdictespera uma chamada! Obrigado!
Chris W.
4
Isso foi marcado como uma possível duplicata de outra pergunta ... mas não era minha pergunta original. Eu sabia como criar um padrão de dois níveis; o que eu não sabia era como torná-lo recursivo. Esta resposta é, de fato, semelhante a stackoverflow.com/questions/5029934/… #
11337 Corley Brigman
Uma desvantagem da abordagem lambda é que os objetos que ela gera não podem ser decapados ... mas você pode contornar isso lançando regularmente dict(result)antes da picles
CpILL
54

Há um truque bacana para fazer isso:

tree = lambda: defaultdict(tree)

Então você pode criar seu xcom x = tree().

BrenBarn
fonte
22

Semelhante à solução do BrenBarn, mas não contém o nome da variável treeduas vezes, por isso funciona mesmo após alterações no dicionário de variáveis:

tree = (lambda f: f(f))(lambda a: (lambda: defaultdict(a(a))))

Então você pode criar cada novo xcom x = tree().


Para a defversão, podemos usar o escopo de fechamento de função para proteger a estrutura de dados da falha em que as instâncias existentes param de funcionar se o treenome for recuperado. Se parece com isso:

from collections import defaultdict

def tree():
    def the_tree():
        return defaultdict(the_tree)
    return the_tree()
pts
fonte
4
vou ter que pensar sobre isso (é um pouco mais complexo). mas acho que o seu ponto é que, se x = tree (), mas alguém aparece mais tarde e tree = None, esse ainda funcionaria e o que não funcionaria?
Corley Brigman
11

Eu também proporia uma implementação mais no estilo OOP, que suporta aninhamento infinito, além de formatada corretamente repr.

class NestedDefaultDict(defaultdict):
    def __init__(self, *args, **kwargs):
        super(NestedDefaultDict, self).__init__(NestedDefaultDict, *args, **kwargs)

    def __repr__(self):
        return repr(dict(self))

Uso:

my_dict = NestedDefaultDict()
my_dict['a']['b'] = 1
my_dict['a']['c']['d'] = 2
my_dict['b']

print(my_dict)  # {'a': {'b': 1, 'c': {'d': 2}}, 'b': {}}
Stanislav Tsepa
fonte
1
Arrumado ! Eu adicionei a passagem de *argse **kwargsque permite que ele funcione como o defaultdict, ou seja, para criar um ditado com argumentos de palavra-chave. Isso é útil para a passagem NestedDefaultDictparajson.load
Ciprian Tomoiagă
0

aqui está uma função recursiva para converter um ditado padrão recursivo em um ditado normal

def defdict_to_dict(defdict, finaldict):
    # pass in an empty dict for finaldict
    for k, v in defdict.items():
        if isinstance(v, defaultdict):
            # new level created and that is the new value
            finaldict[k] = defdict_to_dict(v, {})
        else:
            finaldict[k] = v
    return finaldict

defdict_to_dict(my_rec_default_dict, {})
Dr. XD
fonte
0

Baseei isso na resposta de Andrew aqui. Se você deseja carregar dados de um json ou de um dict existente no nester defaultdict, consulte este exemplo:

def nested_defaultdict(existing=None, **kwargs):
    if existing is None:
        existing = {}
    if not isinstance(existing, dict):
        return existing
    existing = {key: nested_defaultdict(val) for key, val in existing.items()}
    return defaultdict(nested_defaultdict, existing, **kwargs)

https://gist.github.com/nucklehead/2d29628bb49115f3c30e78c071207775

nucklehead
fonte