Я пытаюсь нормализовать словарь, содержащий несколько списков. В качестве 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'
}
Нет, этот ответ не превращает значения списка в отдельные значения. Это сгладит список в список.
Я смутно припоминаю, что существовал довольно краткий способ сделать это с помощью привязок jq.
Где/как ваш код пытается сгенерировать все комбинации, в этом примере, phone с address?
@CooperativistKid, для ясности: при последнем вводе данных, поскольку у вас есть два разных продукта, ваш результат удвоится, с одним набором всего остального для каждого продукта, верно? Проверьте вывод с моим ответом ниже для справки.






В какой части моей рекурсии я ошибаюсь, поэтому она не генерирует желаемый результат?
Можно сказать, что это идет не так, когда результат добавляется до того, как известны все ключи, даже не исправляя этот результат. Однако я не вижу способа исправить это без существенных изменений данного кода.
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 находится на втором уровне.
Хорошо, я добавил строки кода для нового случая словаря, непосредственно содержащегося в словаре.
Мне потребовалось некоторое время, чтобы понять это правильно, но проверьте это.
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'}
Обновлено: пары ключ-значение сопоставлены с диктовками в соответствии с требованиями, что сделало код более аккуратным.
Это красиво.
Я с нетерпением ждал возможности опробовать сопоставление шаблонов Python, и это просто потрясающе!
Отвечает ли это на ваш вопрос? Python Flatten dict с сохранением того же имени ключа после Flatten