Python: создать вложенный dict из строк пар ключ: значение

У меня такой изречение:

{"key1:key2[0]:key3[0]": "1234",
 "key1:key2[0]:key4[0]:key5": "4567",
 "key1:key2[1]:key3[0]": "789",
 "key1:key2[1]:key4[1]:key5": "12345"}

Ключи - это описательный способ представления происхождения каждого конечного значения в целевом dict. : отделяет родительский ключ от его дочернего ключа, [] означает, что значение предыдущих ключей является списком, а индекс находится между фигурными скобками.

Учитывая это, как я могу построить такой диктатор, как

{
   "key1":{
      "key2":[
         {
            "key3":["1234"],
            "key4":[{"key5":"4567"}]
         },
         {
            "key3":["789"],
            "key4":[{"key5":"12345"}]
         }
      ]
   }
}

Я пытался сделать что-то вроде этого:

result_dict = {}

def populate(target_path, value):
    current_point_in_path = None
    t = result_dict
    target_path = target_path.split(":")
    for i, each_key in enumerate(target_path):
        list_index = re.findall(r'\[(.*?)\]', each_key)
        if len(list_index) > 1:
            raise Exception("not allowed")
        elif len(list_index) == 1:
            index = int(list_index[0])
            key_before = each_key.split(index)[0]
            if not isinstance(result_dict[key_before], list):
                t = t.setdefault(key_before, [])
                if i+1 == len(target_path):
                    # the issue is that this insert won't return a pointer to the current index element like setdefault would do
                    # alternate soultions are wc
                    t.insert(index, value)
                else:
                    t.insert(index, {})

        else:
            if i + 1 == len(target_path):
                t = t.setdefault(each_key, value)
            else:
                t = t.setdefault(each_key, {})

Я не могу завершить здесь части кода, возможно, мне понадобится лучший дизайн с моим описательным языком. Любые предложения приветствуются.

«Возможно, мне понадобится лучший дизайн с моим описательным языком. Любые предложения приветствуются». Откуда берутся данные исходного словаря? Потому что, если вы можете, я бы посоветовал вам использовать стандарт JSON для хранения такой многоуровневой структуры данных. docs.python.org/2/library/json.html С JSON очень легко выгружать (сериализовать) и загружать (десериализовать) такие структуры.

Louis Durand 30.04.2018 14:49

Я имею в виду, что если вы можете изменить структуру входных данных на другую структуру, я бы порекомендовал вам использовать для этого JSON. Тогда «декодировать» часть строки JSON в многоуровневый словарь очень просто: json.loads (json_string)! :)

Louis Durand 30.04.2018 14:53
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
2
118
2

Ответы 2

Вы можете использовать itertools.groupby с рекурсией:

import re, itertools
d = {"key1:key2[0]:key3": "1234", "key1:key2[0]:key4": "4567", "key1:key2[1]:key3": "789", "key1:key2[1]:key4": "12345"}
new_d = [(re.findall('\w+', a), b) for a, b in d.items()]
def last_group(d):
  return [{a[-1]:c for a, c in list(b)} for _, b in itertools.groupby(sorted(d, key=lambda x:x[0][1]), key=lambda x:x[0][1])]

def group_data(d):
   return {a:(lambda x:group_data([(c[1:], d) for c, d in x]) if all(len(c) > 3 for c, _ in x) else last_group(x))(list(b)) for a, b in itertools.groupby(sorted(d, key=lambda x:x[0][0]), key=lambda x:x[0][0])}

print(group_data(new_d))

Выход:

{'key1': {'key2': [{'key3': '1234', 'key4': '4567'}, {'key3': '789', 'key4': '12345'}]}}
ValueError: too many values to unpack на return {a:(lambda x:group_data([(c[1:], d) for c, d in x]) if all(len(c) > 3 for c, _ in x) else last_group(x))(list(b)) for a, b in itertools.groupby(sorted(d, key=lambda x:x[0][0]), key=lambda x:x[0][0])}
void 30.04.2018 15:48

@void Странно, у меня работает в обеих версиях Python. Если вы используете совершенно другой ввод, опубликуйте его.

Ajax1234 30.04.2018 15:49

Моя ошибка. Поправил ввод и все работает. Но код ломается, если у меня есть вложенные списки, как в случае с примером, который я добавил (отредактировал).

void 30.04.2018 16:16

Если бы вы могли улучшить ответ, я бы отметил, что это принято, поскольку решение itertools мне кажется более элегантным.

void 03.05.2018 11:55

Вы можете использовать этого монстра:

def populate(result_dict, target_path, value):
    # split path
    target_path = re.findall(r"[^:]+?(?=\[|:|$)|\[\d+?\]", target_path)
    # prepare path
    for i, element in enumerate(target_path):
        if element[0] == "[" and element[-1] == "]":
            element = int(element[1:-1])
        target_path[i] = element
    current = result_dict
    for i, element in enumerate(target_path[:-1]):
        if isinstance(element, str):  # dict index
            if element not in current:  # create new entry
                if isinstance(target_path[i + 1], str):  # next is a dict
                    current[element] = {}
                else:  # next is a list
                    current[element] = []
        elif isinstance(element, int):  # list index
            if element >= len(current):  # create new entry
                current.extend(None for _ in range(element-len(current)+1))
            if current[element] is None:
                if isinstance(target_path[i + 1], str):  # next is a dict
                    current[element] = {}
                else:  # next is a list
                    current[element] = []
        current = current[element]
    if isinstance(target_path[-1], int):
        current.append(value)
    else:
        current[target_path[-1]] = value

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

result_dict = {}
for key, value in {"key1:key2[0]:key3[0]": "1234",
                   "key1:key2[0]:key4[0]:key5": "4567",
                   "key1:key2[1]:key3[0]": "789",
                   "key1:key2[1]:key4[1]:key5": "12345"}.items():
    populate(result_dict, key, value)
print(json.dumps(result_dict, indent=4))

печатает:

{
    "key1": {
        "key2": [
            {
                "key3": [
                    "1234"
                ],
                "key4": [
                    {
                        "key5": "4567"
                    }
                ]
            },
            {
                "key3": [
                    "789"
                ],
                "key4": [
                    null,
                    {
                        "key5": "12345"
                    }
                ]
            }
        ]
    }
}

Извините, но не могли бы вы взглянуть на обновленный пример? Я хочу иметь дело с вложенными списками.

void 30.04.2018 16:26

@void Хорошо, редактировал, теперь работает. Проблема заключалась в индексе пропуска key1:key2[1]:key4[0], который теперь будет None.

MegaIng 30.04.2018 16:53

Другие вопросы по теме