Многоуровневый defaultdict с переменной глубиной? - PullRequest
55 голосов
/ 20 марта 2011

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

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

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

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

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

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

Ответы [ 10 ]

140 голосов
/ 02 января 2012

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

from collections import defaultdict

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

nest[0][1][2][3][4][5] = 6
16 голосов
/ 20 марта 2011

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

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, чтобы прочитать его как структуру вложенных словарей.

9 голосов
/ 20 марта 2011

Я бы сделал это с подклассом 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 ожидает фабричную функцию во время инициализации, но во время инициализации, нет никакого способа описать тот же по умолчанию.Вышеупомянутая конструкция делает то же самое, что и dict по умолчанию, но, поскольку это именованный класс (NestedDict), она может ссылаться на себя при обнаружении пропущенных ключей.Также возможно создать подкласс defaultdict и переопределить __init__.

6 голосов
/ 16 декабря 2012

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

# Define recursive dictionary
tree = lambda: defaultdict(tree)

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

# Create instance
mydict = tree()

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

# Print
import prettyprint
prettyprint.pp(tree)

Выход:

{
  "a": 1, 
  "b": {
    "a": 1
  }, 
  "c": {},
  "d": {
    "a": {
      "b": 0
    }
  }
}
4 голосов
/ 30 января 2014

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

def dict_factory():
   return defaultdict(dict_factory)

your_dict = dict_factory()

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

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

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

3 голосов
/ 15 сентября 2014

Дэн О'Хойгинн опубликовал очень хорошее решение в своем журнале в 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: {}}}}}}
2 голосов
/ 26 августа 2014

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

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
1 голос
/ 18 декабря 2015

Добавить к @Hugo
Для максимальной глубины:

l=lambda x:defaultdict(lambda:l(x-1)) if x>0 else defaultdict(dict)
arr = l(2)
0 голосов
/ 06 января 2019

Этого можно добиться с помощью рекурсивного defaultdict.

from collections import defaultdict

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

Важно защитить заводское имя по умолчанию, the_tree здесь, в закрытии ("private"область действия локальной функции).Избегайте использования однострочной версии 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
0 голосов
/ 20 марта 2011

Есть table['A']=defaultdict().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...