Как преобразовать список словарей в дерево, подобное словарю nest?

#python #python-3.x #dictionary

#python #python-3.x #словарь

Вопрос:

У меня есть мои данные в этом формате —

 data = {
    "label": "xyz.com",
    "children": [
        {
            "parent": "abc.com",
            "label": "user-3",
            "depth": 1
        },
        {

            "parent": "xyz.com",
            "label": "abc.com",
            "depth": 0
        },
        {

            "parent": "xyz.com",
            "label": "user-1",
            "depth": 0
        }
    ]
}
 

Я хочу сгенерировать результат, который выглядит следующим образом (вложенный словарь) —

 result = {
    "label": "xyz.com",
    "children": [
         {

             "parent": "xyz.com",
             "label": "user-1",
             "depth": 0
         },
        {

            "parent": "xyz.com",
            "label": "abc.com",
            "depth": 0,
            "children": [
                {
                    "parent": "abc.com",
                    "label": "user-3",
                    "depth": 1
                }
            ]
         }

    ]
}
 

Исходный словарь преобразуется во вложенный словарь с использованием метокparent и depth .

Кроме того, может быть любое количество дочерних элементов. Например —

 result = {
    "lable": "xyz.com",
    "children": [
         {
             "parent": "xyz.com",
             "label": "user-1",
             "depth": 0
         },
        {

            "parent": "xyz.com",
            "label": "abc.com",
            "depth": 0,
            "children": [
                {
                    "parent": "abc.com",
                    "label": "user-3",
                    "depth": 1,
                    "children": [
                        {...}, {...}
                    ]
                }
            ]
         }
    ]
}
 

Не уверен, что рекурсия — это правильный путь или есть какое-то другое решение.

Спасибо за помощь.

Ответ №1:

Гораздо более эффективным и однопроходным линейным подходом является создание dict, который сопоставляет метки с узлами, чтобы вы могли легко получить родительский узел, просто просмотрев родительскую метку в dict и добавив узел к children вложенному списку родительского узла. Используется dict.setdefault для инициализации записи dict для родительского узла сначала, если итерация поступает на дочерний узел раньше, чем его родительский узел, чтобы родительский узел мог позже обновить себя с помощью уже существующей children записи:

 result = {'label': data['label']}
nodes = {data['label']: result}
for node in data['children']:
    node.update(nodes.get(node['label'], {}))
    nodes[node['label']] = node
    nodes.setdefault(node['parent'], {}).setdefault('children', []).append(node)
 

result становится:

 {'label': 'xyz.com',
 'children': [{'parent': 'xyz.com',
               'label': 'abc.com',
               'depth': 0,
               'children': [{'parent': 'abc.com',
                             'label': 'user-3',
                             'depth': 1}]},
              {'parent': 'xyz.com', 'label': 'user-1', 'depth': 0}]}
 

Ответ №2:

Вы можете использовать рекурсию:

 data = {'label': 'xyz.com', 'children': [{'parent': 'abc.com', 'label': 'user-3', 'depth': 1}, {'parent': 'xyz.com', 'label': 'abc.com', 'depth': 0}, {'parent': 'xyz.com', 'label': 'user-1', 'depth': 0}]}
def group(d):
  new_d = [i for i in d if all(c.get('label') != i.get('parent') for c in d)]
  c = [(lambda x:{a:b for a, b in x.items() if a != 'children' or b})({**i, 'children':[*i.get('children', []), *[j for j in d if j.get('parent') == i['label']]]}) for i in new_d]
  return [i if 'children' not in i else {**i, 'children':group(i['children'])} for i in c]
 

 import json
print(json.dumps(group([data]), indent=4))
 

Вывод:

 [
  {
    "label": "xyz.com",
    "children": [
        {
            "parent": "xyz.com",
            "label": "abc.com",
            "depth": 0,
            "children": [
                {
                    "parent": "abc.com",
                    "label": "user-3",
                    "depth": 1
                }
            ]
        },
        {
            "parent": "xyz.com",
            "label": "user-1",
            "depth": 0
        }
      ]
   }
]
 

Ответ №3:

Более простой реализацией рекурсивного подхода было бы воспользоваться преимуществами ключей, уже имеющихся в dicts, и изменять dicts вместо создания новых:

 def convert(nodes, parent):
    children = [convert(nodes, node) for node in nodes if node['parent'] == parent['label']]
    if children:
        parent['children'] = children
    return parent
 

итак, это convert(data.pop('children'), data) возвращает:

 {'label': 'xyz.com',
 'children': [{'parent': 'xyz.com',
               'label': 'abc.com',
               'depth': 0,
               'children': [{'parent': 'abc.com',
                             'label': 'user-3',
                             'depth': 1}]},
              {'parent': 'xyz.com', 'label': 'user-1', 'depth': 0}]}