Многоуровневый defaultdict с переменной глубиной?

У меня есть большой список вроде:

[A][B1][C1]=1
[A][B1][C2]=2
[A][B2]=3
[D][E][F][G]=4

Я хочу построить многоуровневый dict, например:

A
--B1
-----C1=1
-----C2=1
--B2=3
D
--E
----F
------G=4

Я знаю, что если я использую рекурсивный defaultdict, я могу написать table[A][B1][C1]=1, table[A][B2]=2, но это работает только в том случае, если я жестко кодирую эти инструкции insert.

Во время разбора списка я не знаю, сколько [] мне нужно позвонить table[key1][key2][...].

Ответ 1

Вы можете сделать это, даже не определяя класс:

from collections import defaultdict

nested_dict = lambda: defaultdict(nested_dict)
nest = nested_dict()

nest[0][1][2][3][4][5] = 6

Ответ 2

В вашем примере сказано, что на любом уровне может быть значение, а также словарь подэлементов. Это называется tree, и для них доступно множество реализаций. Это одно:

from collections import defaultdict
class Tree(defaultdict):
    def __init__(self, value=None):
        super(Tree, self).__init__(Tree)
        self.value = value

root = Tree()
root.value = 1
root['a']['b'].value = 3
print root.value
print root['a']['b'].value
print root['c']['d']['f'].value

Выходы:

1
3
None

Вы можете сделать что-то подобное, записав ввод в JSON и используя json.load, чтобы прочитать его как структуру вложенных словарей.

Ответ 3

Я бы сделал это с подклассом dict, который определяет __missing__:

>>> class NestedDict(dict):
...     def __missing__(self, key):
...             self[key] = NestedDict()
...             return self[key]
...
>>> table = NestedDict()
>>> table['A']['B1']['C1'] = 1
>>> table
{'A': {'B1': {'C1': 1}}}

Вы не можете сделать это напрямую с defaultdict, потому что defaultdict ожидает функцию factory во время инициализации, но во время инициализации нет способ описать один и тот же defaultdict. Вышеупомянутая конструкция делает то же самое, что и default dict, но поскольку это именованный класс (NestedDict), он может ссылаться на себя, поскольку встречаются недостающие ключи. Также возможно подклассы defaultdict и переопределить __init__.

Ответ 4

Я думаю, что самая простая реализация рекурсивного словаря - это. Только конечные узлы могут содержать значения.

# Define recursive dictionary
from collections import defaultdict
tree = lambda: defaultdict(tree)

Использование:

# Create instance
mydict = tree()

mydict['a'] = 1
mydict['b']['a'] = 2
mydict['c']
mydict['d']['a']['b'] = 0

# Print
import prettyprint
prettyprint.pp(mydict)

Выход:

{
  "a": 1, 
  "b": {
    "a": 1
  }, 
  "c": {},
  "d": {
    "a": {
      "b": 0
    }
  }
}

Ответ 5

Это эквивалентно приведенному выше, но избегая обозначения лямбда. Может быть, легче читать?

def dict_factory():
   return defaultdict(dict_factory)

your_dict = dict_factory()

Также - из комментариев - если вы хотите обновить из существующего dict, вы можете просто вызвать

your_dict[0][1][2].update({"some_key":"some_value"})

Чтобы добавить значения в dict.

Ответ 6

Dan O'Huiginn опубликовал очень хорошее решение в своем журнале в 2010 году:

http://ohuiginn.net/mt/2010/07/nested_dictionaries_in_python.html

>>> class NestedDict(dict):
...     def __getitem__(self, key):
...         if key in self: return self.get(key)
...         return self.setdefault(key, NestedDict())


>>> eggs = NestedDict()
>>> eggs[1][2][3][4][5]
{}
>>> eggs
{1: {2: {3: {4: {5: {}}}}}}

Ответ 7

Несколько другая возможность, которая позволяет регулярную инициализацию словаря:

from collections import defaultdict

def superdict(arg=()):
    update = lambda obj, arg: obj.update(arg) or obj
    return update(defaultdict(superdict), arg)

Пример:

>>> d = {"a":1}
>>> sd = superdict(d)
>>> sd["b"]["c"] = 2

Ответ 8

Чтобы добавить к @Hugo
Чтобы иметь максимальную глубину:

l=lambda x:defaultdict(lambda:l(x-1)) if x>0 else defaultdict(dict)
arr = l(2)

Ответ 9

Вы можете достичь этого с помощью рекурсивного defaultdict.

from collections import defaultdict

def tree():
    def the_tree():
        return defaultdict(the_tree)
    return the_tree()

Важно защитить заводское имя по умолчанию, the_tree здесь, в замыкании ("частная" область видимости локальной функции). Избегайте использования однолинейной lambda версии, которая содержит ошибки из-за поздних закрытий привязки Python, и вместо этого реализуйте ее с помощью def.

Принятый ответ, использующий лямбду, имеет недостаток, когда экземпляры должны полагаться на имя nested_dict существующее во внешней области видимости. Если по какой-либо причине фабричное имя не может быть разрешено (например, оно было восстановлено или удалено), то ранее существующие экземпляры также будут слегка повреждены:

>>> nested_dict = lambda: defaultdict(nested_dict)
>>> nest = nested_dict()
>>> nest[0][1][2][3][4][6] = 7
>>> del nested_dict
>>> nest[8][9] = 10
# NameError: name 'nested_dict' is not defined

Ответ 10

У вас table['A']=defaultdict().