Смещение словарей во вложенных словарях

У меня есть следующий список вложенных словарей и списков. Я хочу создать новую родительскую категорию (L0) под названием 'food' и сдвинуть все значения в словарях fruit и vegs на один шаг вниз (так, чтобы 'L0': 'fruit' превратился в 'L1': 'fruit', 'L1': 'banana' стал 'L2': 'banana' и т. Д.).

D = [{
        "L0": "fruit",
        "L1_list": [
            {
                "L1": "banana"
            },
            {
                "L1": "apple", 
                "L2_list": [
                    {
                        "L2": "Green apple"
                    }, 
                    {
                        "L2": "Red apple"
                    }
                ]
            }, 
            {
                "L1": "kiwi"
            }
        ]
    },
    {
        "L0": "vegs", 
        "L1_list": [
            {
                "L1": "potato"
            }, 
            {
                "L1": "carrot"
            }
        ]
    }]

Исключенный вывод должен выглядеть так:

Expected_output = [
    {
        "L0": "food",
        "L1_list": [
            {
                "L1": "fruit",
                "L2_list": [
                    {
                        "L2": "banana"
                    },
                    {
                        "L2": "apple",
                        "L3_list": [
                            {
                                "L3": "Green apple"
                            },
                            {
                                "L3": "Redapple"
                            }
                        ]
                    },
                    {
                        "L2": "kiwi"
                    }
                ]
            },
            {
                "L1": "vegs",
                "L2_list": [
                    {
                        "L2": "potato"
                    },
                    {
                        "L2": "carrot"
                    }
                ]
            }
        ]
    }
]

Теперь, поскольку мои словари могут различаться по размеру и глубине, мне нужно программное решение. Поэтому я подумал, что создам рекурсивную функцию, которая выполняет итерацию, пока не достигнет конца дерева. Когда функция достигает конца определенной ветви, она добавляет к ключу 1 (L0 -> L1, L1_list -> L2_list). Хотя процесс действительно сдвигает все на один уровень вниз, я не могу понять, как восстановить исходную структуру. В частности, я не могу вернуть детей в их список.

Final_list = []
def digger(list_to_dig):
    import re
    for x in list_to_dig:
        for k,v in x.items():
            if isinstance(v, list):
                print("keep digging")
                digger(v)
            elif isinstance(x, dict):
                new_D = {}
                new_k = "L" + str(int(re.sub("L", "", k)) + 1)
                new_D[new_k] = v
                temp = re.sub("L", "", k)
                new_child_list = "L" + str(int(re.sub("_list", "", temp)) + 2) + "_list"
                new_D[new_child_list] = ""
                Final_list.append(new_D)
            else:
                print("no dictionary avail")
                pass
    print("_________")
    print(Final_list)
    print("_________")

    test = digger(D)

Есть предложения, как мне с этим справиться? Большое спасибо

Следуя предложению @ running.t, я попытался использовать метод dict.pop. Однако, поскольку это происходит в рамках итерации, он выталкивает старый ключ, создает и вставляет новый, но на следующей итерации берет только что созданный новый ключ, выталкивает его, создает и вставляет новый новый ключ, и так on (хотя и не заходит в бесконечный цикл).

Вот упрощенный пример, иллюстрирующий проблему:

Шаг 1: создайте новый диктат верхнего уровня

new_top_level = {"L0": "Food"}
new_dict = {}
for k, v in new_top_level.items():
    lst_k = "L" + str(int(re.sub("L", "", ka)) + 1) + "_list"
    new_dict[k] = v
    new_dict[lst_k] = []

Шаг 2 добавьте старое дерево в новый список

old_d = {'L0': 'Fruit', 'L1_list': [{'L1': 'Green apple'}, {'L1': 'Red apple'}]}
new_dict[lst_k].append(old_d)

Шаг 3 добавьте 1 ко всем ключам старого дерева

def digger(list_to_update):
    import re
    pattern1 = r"L.$"
    pattern2 = r"L._list"
    for x in list_to_update:
        for k1, v1 in x.items():
            if re.match(pattern1, k1):
                new_k1 = "L" + str(int(re.sub("L", "", k1)) + 1)
                x[new_k1] = x.pop(k1)
            elif re.match(pattern2, k1):
                temp = re.sub("L", "", k1)
                new_k1 = "L" + str(int(re.sub("_list", "", temp)) + 1) + "_list"
                x[new_k1] = x.pop(k1)
                digger(v1)

test = digger(new_dict[lst_k])

Откуда вы взяли этот начальный словарь? Вы сами его построили?

Igle 11.04.2018 14:10

да, я построил этот конкретный словарь вручную. Однако в окончательном решении он будет построен через пользовательский интерфейс конечным пользователем.

Intel_code 11.04.2018 15:05

А как насчет L10?

Mad Physicist 31.03.2020 15:55
5
3
123
2

Ответы 2

Вы не должны создавать новый список и помещать в него все. И на самом деле это то, что вы делаете в очереди:

Final_list.append(new_D)

Вместо этого вам следует рекурсивно перебирать все словари и списки, которые у вас есть (так же, как вы это делаете сейчас), и если объект является dict, переименуйте все ключи в этом dict соответственно.

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

new_k = "L"+str(int(re.sub("L","",k))+1) 
x[new_key] = x.pop(k)

И, наконец, после завершения раскопок всех D вы должны поместить модифицированный D в новый список Expected_output.

Спасибо! очень полезно. Я не знаком с функцией pop, но изучу ее и расскажу, как она работает

Intel_code 11.04.2018 14:45

На год позже, я знаю, но давайте сделаем беглый анализ проблемы в прозе. У тебя есть словарь. Словарь может иметь два типа ключей: L* и L*_list. В обоих случаях * - целое число. L* всегда будет иметь строковое значение. L*_list всегда будет иметь значение списка словарей. Ваша цель - рекурсивно увеличивать целые числа в именах ключей.

Очевидно, что-то подобное хорошо поддается рекурсии. Вы рекурсивно переходите к каждому элементу значения L*_list. Рекурсия заканчивается, когда вы получаете список словарей, не имеющих ключей L*_list. В этом случае вы только увеличиваете ключи L* и возвращаетесь. До сих пор мы полностью согласны, поскольку все, что я сказал, уже поставлено под вопрос.

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

На верхнем уровне есть особый случай, поскольку вы хотите поместить все в новую категорию food. Это не проблема, поскольку рекурсивное решение вернет значение для нового ключа L1_list.

Вот простой пример реализации:

def increment_keys(d):
    def process_key(key, value):
        key = f'L{int(key[1:]) + 1}'
        return key, value

    def process_list(key, value):
        key = f'L{int(key[1:-5]) + 1}_list'
        value = [increment_keys(d) for d in value]
        return key, value

    def process(key, value):
        if key.endswith('_list'):
            return process_list(key, value)
        return process_key(key, value)

    return dict(process(key, value) for key, value in d.items())

expected_output = [{'L0': 'food', **increment_keys({'L0_list': D})}]

Вы можете встроить вложенную функцию process в генератор, который передает значение, возвращаемое increment_keys, с помощью тернарного оператора. Я не думаю, что это улучшит читаемость, но это сэкономит вам около четырех строк:

return dict(process_list(k, v) if k.endswith('_list') else process_key(k, v) for k, v in d)

Теперь, если вам абсолютно необходимо сделать это на месте, лучшим способом было бы заморозить ключи каждого словаря перед повторением. Если вы перебираете замороженные ключи, pop и __setitem__ не вызовут у вас никаких проблем.

Поскольку вы никогда не получите дубликатов между исходными и увеличенными ключами на заданном уровне, вам не нужно обращать особое внимание на потерю предыдущих значений (например, если у вас были L1 и L2 в одном dict, а сначала увеличили L1.

Вот пример рекурсии на месте:

def increment_keys(obj):
    def process(d):
        for key in list(d.keys()):
            value = d.pop(key)
            if key.endswith('_list'):
                key = f'L{int(key[1:-5]) + 1}_list'
                increment_keys(value)
            else:
                key = f'L{int(key[1:]) + 1}'
            d[key] = value

    for d in obj:
        process(d)

increment_keys(D)
expected_output = [{'L0': 'food', 'L1_list': D}]

В соответствии с соглашением Python я ничего не возвращал из функции на месте.

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