Плоский словарь Python, содержащий списки

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

test_dict = {
    'name' : 'john',
    'age' : 20,
    'addresses' : [
        {
            'street': 'XXX',
            'number': 123,
            'complement' : [
                'HOUSE',
                'NEAR MARKET'
            ]
        },
        {
            'street': 'YYY',
            'number': 456,
            'complement' : [
                'AP',
                'NEAR PARK'
            ]
        },
    ],
    'phones' : [
        '123456'
    ],
    'gender' : 'MASC'
}

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

{'name': 'john', 'age': 20, 'street': 'XXX', 'number': 123, 'complement': 'HOUSE', 'phones': '123456', 'gender' : 'MASC'}
{'name': 'john', 'age': 20, 'street': 'XXX', 'number': 123, 'complement': 'NEAR MARKET', 'phones': '123456', 'gender' : 'MASC'}
{'name': 'john', 'age': 20, 'street': 'YYY', 'number': 456, 'complement': 'AP', 'phones': '123456', 'gender' : 'MASC'}
{'name': 'john', 'age': 20, 'street': 'YYY', 'number': 456, 'complement': 'NEAR PARK', 'phones': '123456', 'gender' : 'MASC'}

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

{'name': 'john', 'age': 20, 'street': 'XXX', 'number': 123, 'complement': 'HOUSE'}
{'name': 'john', 'age': 20, 'street': 'XXX', 'number': 123, 'complement': 'NEAR MARKET'}
{'name': 'john', 'age': 20, 'street': 'YYY', 'number': 456, 'complement': 'AP'}
{'name': 'john', 'age': 20, 'street': 'YYY', 'number': 456, 'complement': 'NEAR PARK'}
{'name': 'john', 'age': 20, 'phones': '123456'}

Мой код Python (MVCE):

def get_list_values(lista, dicionario, key_name, results):
    if len(lista) > 0:
        for l in lista:
            if isinstance(l, dict):
                search_values(l, dicionario.copy(), results)
            else:
                dicionario_metodo = dicionario.copy()
                dicionario_metodo[key_name] = l
                results.append(dicionario_metodo)

def search_values(dicionario, test, results):
    for k, v in dicionario.items():
        if isinstance(v, list):
            get_list_values(v, test, k, results )
        else: 
            test[k] = v
    if not any(isinstance(v, list) for v in dicionario.values()):
        results.append(test.copy())
    return results 


test = {}
results = []
for r in search_values(test_dict, test, results):
    print(r)

В какой части моей рекурсии я ошибаюсь, поэтому она не генерирует желаемый результат?


Редактировать 1:

test_dict = {
    'name' : 'john',
    'age' : 20,
    'addresses' : [
        {
            'street': 'XXX',
            'number': 123,
            'complement' : [
                'HOUSE',
                'NEAR MARKET'
            ]
        },
        {
            'street': 'YYY',
            'number': 456,
            'complement' : [
                'AP',
                'NEAR PARK'
            ]
        },
    ],
    'type' : {
        'category': 'G123',
        'products': [
            'test1',
            'test2'
        ]
    },
    'phones' : [
        '123456'
    ],
    'gender' : 'MASC'
}

Отвечает ли это на ваш вопрос? Python Flatten dict с сохранением того же имени ключа после Flatten

folen gateis 23.05.2024 21:54

Нет, этот ответ не превращает значения списка в отдельные значения. Это сгладит список в список.

CooperativistKid 23.05.2024 21:57

Я смутно припоминаю, что существовал довольно краткий способ сделать это с помощью привязок jq.

chepner 24.05.2024 13:43

Где/как ваш код пытается сгенерировать все комбинации, в этом примере, phone с address?

Scott Hunter 24.05.2024 14:18

@CooperativistKid, для ясности: при последнем вводе данных, поскольку у вас есть два разных продукта, ваш результат удвоится, с одним набором всего остального для каждого продукта, верно? Проверьте вывод с моим ответом ниже для справки.

Rodrigo Rodrigues 28.05.2024 04:19
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
5
146
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

В какой части моей рекурсии я ошибаюсь, поэтому она не генерирует желаемый результат?

Можно сказать, что это идет не так, когда результат добавляется до того, как известны все ключи, даже не исправляя этот результат. Однако я не вижу способа исправить это без существенных изменений данного кода.

def get_list_values(lista, key_name):
    results = []
    for l in lista:
        if isinstance(l, dict):
            results += search_values(l)
        else:
            results += [{key_name: l}]
    return results

def search_values(dicionario):
    results = [{}]
    for k, v in dicionario.items():
      results_metodo = []
      for r in results: # multiply current results by new values
        if isinstance(v, list):
            for d in get_list_values(v, k): results_metodo += [dict(r, **d)]
        elif isinstance(v, dict):
            for d in search_values(v): results_metodo += [dict(r, **d)]
        else:
            results_metodo += [dict(r, **{k: v})]
      results = results_metodo
    return results 

for r in search_values(test_dict): print(r)

Привет, @Армали! Спасибо за Ваш ответ. Для этого типа dict это сработало отлично, но если вы добавите ключ редактирования 1 на первый уровень dict, это не будет работать, поскольку ключ products находится на втором уровне.

CooperativistKid 24.05.2024 14:45

Хорошо, я добавил строки кода для нового случая словаря, непосредственно содержащегося в словаре.

Armali 27.05.2024 09:02
Ответ принят как подходящий

Мне потребовалось некоторое время, чтобы понять это правильно, но проверьте это.

def flat(out, *kvs):
    match kvs:
        case []: yield out
        case (k, []), *kvs: yield from flat(out, *kvs)
        case (k, list(l)), *kvs: 
            for v in l: yield from flat(out, (k, v), *kvs)
        case (_, dict(d)), *kvs: yield from flat(out, *d.items(), *kvs)
        case (k, v), *kvs: yield from flat([*out, (k, v)], *kvs)
        case _: raise ValueError("Invalid")

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

Вы можете попробовать это следующим образом:

x = map(dict, flat([], (..., test_dict)))
print(*x, sep='\n')

# {'name': 'john', 'age': 20, 'street': 'XXX', 'number': 123, 'complement': 'HOUSE', 'phones': '123456', 'gender': 'MASC'}
# {'name': 'john', 'age': 20, 'street': 'XXX', 'number': 123, 'complement': 'NEAR MARKET', 'phones': '123456', 'gender': 'MASC'}
# {'name': 'john', 'age': 20, 'street': 'YYY', 'number': 456, 'complement': 'AP', 'phones': '123456', 'gender': 'MASC'}
# {'name': 'john', 'age': 20, 'street': 'YYY', 'number': 456, 'complement': 'NEAR PARK', 'phones': '123456', 'gender': 'MASC'}

С вашими вторыми входными данными результат будет таким:

# {'name': 'john', 'age': 20, 'street': 'XXX', 'number': 123, 'complement': 'HOUSE', 'category': 'G123', 'products': 'test1', 'phones': '123456', 'gender': 'MASC'}
# {'name': 'john', 'age': 20, 'street': 'XXX', 'number': 123, 'complement': 'HOUSE', 'category': 'G123', 'products': 'test2', 'phones': '123456', 'gender': 'MASC'}
# {'name': 'john', 'age': 20, 'street': 'XXX', 'number': 123, 'complement': 'NEAR MARKET', 'category': 'G123', 'products': 'test1', 'phones': '123456', 'gender': 'MASC'}
# {'name': 'john', 'age': 20, 'street': 'XXX', 'number': 123, 'complement': 'NEAR MARKET', 'category': 'G123', 'products': 'test2', 'phones': '123456', 'gender': 'MASC'}
# {'name': 'john', 'age': 20, 'street': 'YYY', 'number': 456, 'complement': 'AP', 'category': 'G123', 'products': 'test1', 'phones': '123456', 'gender': 'MASC'}
# {'name': 'john', 'age': 20, 'street': 'YYY', 'number': 456, 'complement': 'AP', 'category': 'G123', 'products': 'test2', 'phones': '123456', 'gender': 'MASC'}
# {'name': 'john', 'age': 20, 'street': 'YYY', 'number': 456, 'complement': 'NEAR PARK', 'category': 'G123', 'products': 'test1', 'phones': '123456', 'gender': 'MASC'}
# {'name': 'john', 'age': 20, 'street': 'YYY', 'number': 456, 'complement': 'NEAR PARK', 'category': 'G123', 'products': 'test2', 'phones': '123456', 'gender': 'MASC'}

Обновлено: пары ключ-значение сопоставлены с диктовками в соответствии с требованиями, что сделало код более аккуратным.

Это красиво.

Armali 28.05.2024 07:50

Я с нетерпением ждал возможности опробовать сопоставление шаблонов Python, и это просто потрясающе!

Rodrigo Rodrigues 28.05.2024 08:29

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