2014-11-13 3 views
2

Я прошу вас помочь, чтобы решить проблему, когда я теряю сознание с 3-х дней.Рекурсия Python с dicts

Это довольно трудно объяснить, но я пытаюсь преобразовать этот Dict:

{ 
    'contrats': [ 
     { 
      'code': '1234567890', 
      'produits': [ 
       { 
        'code': 'MAGENTA', 
       }, 
       { 
        'code': 'EMERAUDE', 
       }, 
      ], 
      'familles': [ 
       { 
        'code': 'FAMILLE_KLEIN', 
        'personnes': [ 
         { 
          'code': 'MR_KLEIN', 
          'nom': 'KLEIN', 
          'prenom': 'Monsieur', 
          'date_naissance': '01/01/1973', 
          'role_famille': 'A', 
          'roles_contrat': ['A', 'S', 'P'], 
         }, 
         { 
          'code': 'MME_KLEIN', 
          'nom': 'KLEIN', 
          'prenom': 'Madame', 
          'date_naissance': '01/01/1976', 
          'role_famille': 'C', 
          'roles_contrat': ['C'], 
         }, 
         { 
          'code': 'E1_KLEIN', 
          'nom': 'KLEIN', 
          'prenom': 'Enfant 1', 
          'date_naissance': '01/01/1998', 
          'role_famille': 'E', 
          'roles_contrat': ['E'], 
         }, 
         { 
          'code': 'E2_KLEIN', 
          'nom': 'KLEIN', 
          'prenom': 'Enfant 2', 
          'date_naissance': '01/01/2001', 
          'role_famille': 'E', 
          'roles_contrat': ['E'], 
         }, 
        ], 
       }, 
      ], 
     }, 
    ], 
} 

http://dpaste.com/2QFMSYQ

В этом Словаре:

[ 
{ 
    'contrat_code': '1234567890', 
    'produit_code': 'MAGENTA', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'MR_KLEIN', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Monsieur', 
    'personne_date_naissance': '01/01/1973', 
    'personne_role_famille': 'A', 
    'personne_roles_contrat': ['A', 'S', 'P'], 
}, 
{ 
    'contrat_code': '1234567890', 
    'produit_code': 'EMERAUDE', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'MR_KLEIN', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Monsieur', 
    'personne_date_naissance': '01/01/1973', 
    'personne_role_famille': 'A', 
    'personne_roles_contrat': ['A', 'S', 'P'], 
}, 
{ 
    'contrat_code': '1234567890', 
    'produit_code': 'MAGENTA', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'MME_KLEIN', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Madame', 
    'personne_date_naissance': '01/01/1976', 
    'personne_role_famille': 'C', 
    'personne_roles_contrat': ['C'], 
}, 
{ 
    'contrat_code': '1234567890', 
    'produit_code': 'EMERAUDE', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'MME_KLEIN', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Madame', 
    'personne_date_naissance': '01/01/1976', 
    'personne_role_famille': 'C', 
    'personne_roles_contrat': ['C'], 
}, 
{ 
    'contrat_code': '1234567890', 
    'produit_code': 'MAGENTA', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'E1_KLEIN', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Enfant 1', 
    'personne_date_naissance': '01/01/1998', 
    'personne_role_famille': 'E', 
    'personne_roles_contrat': ['E'], 
}, 
{ 
    'contrat_code': '1234567890', 
    'produit_code': 'EMERAUDE', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'E1_KLEIN', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Enfant 1', 
    'personne_date_naissance': '01/01/1998', 
    'personne_role_famille': 'E', 
    'personne_roles_contrat': ['E'], 
}, 
{ 
    'contrat_code': '1234567890', 
    'produit_code': 'MAGENTA', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'E2_KLEIN', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Enfant 2', 
    'personne_date_naissance': '01/01/2001', 
    'personne_role_famille': 'E', 
    'personne_roles_contrat': ['E'], 
}, 
{ 
    'contrat_code': '1234567890', 
    'produit_code': 'EMERAUDE', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'E2_KLEIN', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Enfant 2', 
    'personne_date_naissance': '01/01/2001', 
    'personne_role_famille': 'E', 
    'personne_roles_contrat': ['E'], 
}, 
] 

http://dpaste.com/3S5K4TF

Как вы видите, , Мне нужно объединить все ключи/значения (контекст) t он диктует только на одном уровне.

У меня есть начало алгоритма, но он возвращает только 4 контекста вместо 8 ожидаемых.

def creer_contextes(contexte, sections, ctx=None, done=None): 
    resultats = [] 
    ctx_local = (ctx or {}).copy() 
    vues = (done or set()).copy() 
    sections_ = set(aplatir(sections)) 
    for section in sections_ - vues: 
     for element in contexte.get(section, []): 
     copie = element.copy() 
     normalisation = normaliser(copie, section[:-1], *sections_) 
     ctx_local.update(normalisation) 
     vues.add(section) 
     if vues == sections_: 
      resultats += [ctx_local.copy()] 
     if any(s in element.keys() for s in sections_): 
      resultats += creer_contextes(copie, sections, ctx=ctx_local, done=vues) 
    return resultats 


def aplatir(ilist): 
    return sum(([x] if not isinstance(x, list) else aplatir(x) for x in ilist), []) 


def normaliser(idict, clef, *excludes): 
    return {'{}_{}'.format(clef, key): value for key, value in idict.items() if key not in excludes} 

Благодарим за предоставленную помощь!

+1

Почему желание сделать это рекурсивно? Здесь ключи не могут быть просто закодированы? Ваша структура данных не подходит для рекурсивной обработки, у вас есть продукт из 4 списков, но они частично вложенные, частично смежные. –

+0

И для правильного общего подхода вам нужно будет расширить свои префиксы ключей, чтобы включить иерархию; что, если в структуре есть вторая вложенная структура 'contrats'? –

+0

И почему здесь исключается 'role_contrat'? Я ожидал бы большего умножения для каждого отдельного значения в этих списках. –

ответ

0

Вы можете использовать itertools.product() для обработки смежных списков здесь, рекурсивных в структуру, чтобы расширить словарь результатов по мере продвижения. С помощью itertools.product() вы не должны рекурсию на том же уровне «вложенности» только потому, что существует множество вложенных структур в заданном уровне:

from itertools import product 

def recursive_flatten(d, prefix=''): 
    result = {} 
    nested = {} 

    # extract just the 'flat' keys for the result 
    for key, value in d.iteritems(): 
     result_key = (prefix + '_' + key.rstrip('s')).lstrip('_') 
     if isinstance(value, list) and value and isinstance(value[0], dict): 
      # nested dictionaries are treated recursively 
      nested[result_key] = value 
      continue 
     result[result_key] = value 

    # no nested keys? We are done here 
    if not nested: 
     yield result 
     return 

    # produce a new copy for each combination of nested structures 
    for nested_combos in product(*nested.values()): 
     results = [result] 
     for nested_key, nested_value in zip(nested, nested_combos): 
      # multiply results with nested results 
      results = [ 
       dict(r, **rec_result) 
       for rec_result in recursive_flatten(nested_value, nested_key) 
       for r in results] 
     for result in results: 
      yield result 

Я производить более длинные ключи здесь; префикс вложенных структур с ключом для родителя, рекурсивно. Поэтому все префикс contrat_, тогда данные из списка familles префиксны с contrat_famille_ и т. Д. Если вы этого не хотите, используйте nested[key.rstrip('s')] = value вместо nested[result_key] = value в первом цикле функции.

Однако, я чувствую, что не префикс ключей может привести к созданию конфликтующих ключей (если есть несколько ключей contrat с вложенными структурами на разных уровнях дерева, например).

Это предполагает, что ваша структура данных не менее соответствует; если первым элементом списка является словарь, то все элементов в этом списке являются словарями.

Это дает ожидаемый результат (хотя и с более длинными ключами) для вашего образца:

>>> from pprint import pprint 
>>> pprint(list(recursive_flatten(sample))) 
[{'contrat_code': '1234567890', 
    'contrat_famille_code': 'FAMILLE_KLEIN', 
    'contrat_famille_personne_code': 'MR_KLEIN', 
    'contrat_famille_personne_date_naissance': '01/01/1973', 
    'contrat_famille_personne_nom': 'KLEIN', 
    'contrat_famille_personne_prenom': 'Monsieur', 
    'contrat_famille_personne_role_famille': 'A', 
    'contrat_famille_personne_roles_contrat': ['A', 'S', 'P'], 
    'contrat_produit_code': 'MAGENTA'}, 
{'contrat_code': '1234567890', 
    'contrat_famille_code': 'FAMILLE_KLEIN', 
    'contrat_famille_personne_code': 'MME_KLEIN', 
    'contrat_famille_personne_date_naissance': '01/01/1976', 
    'contrat_famille_personne_nom': 'KLEIN', 
    'contrat_famille_personne_prenom': 'Madame', 
    'contrat_famille_personne_role_famille': 'C', 
    'contrat_famille_personne_roles_contrat': ['C'], 
    'contrat_produit_code': 'MAGENTA'}, 
{'contrat_code': '1234567890', 
    'contrat_famille_code': 'FAMILLE_KLEIN', 
    'contrat_famille_personne_code': 'E1_KLEIN', 
    'contrat_famille_personne_date_naissance': '01/01/1998', 
    'contrat_famille_personne_nom': 'KLEIN', 
    'contrat_famille_personne_prenom': 'Enfant 1', 
    'contrat_famille_personne_role_famille': 'E', 
    'contrat_famille_personne_roles_contrat': ['E'], 
    'contrat_produit_code': 'MAGENTA'}, 
{'contrat_code': '1234567890', 
    'contrat_famille_code': 'FAMILLE_KLEIN', 
    'contrat_famille_personne_code': 'E2_KLEIN', 
    'contrat_famille_personne_date_naissance': '01/01/2001', 
    'contrat_famille_personne_nom': 'KLEIN', 
    'contrat_famille_personne_prenom': 'Enfant 2', 
    'contrat_famille_personne_role_famille': 'E', 
    'contrat_famille_personne_roles_contrat': ['E'], 
    'contrat_produit_code': 'MAGENTA'}, 
{'contrat_code': '1234567890', 
    'contrat_famille_code': 'FAMILLE_KLEIN', 
    'contrat_famille_personne_code': 'MR_KLEIN', 
    'contrat_famille_personne_date_naissance': '01/01/1973', 
    'contrat_famille_personne_nom': 'KLEIN', 
    'contrat_famille_personne_prenom': 'Monsieur', 
    'contrat_famille_personne_role_famille': 'A', 
    'contrat_famille_personne_roles_contrat': ['A', 'S', 'P'], 
    'contrat_produit_code': 'EMERAUDE'}, 
{'contrat_code': '1234567890', 
    'contrat_famille_code': 'FAMILLE_KLEIN', 
    'contrat_famille_personne_code': 'MME_KLEIN', 
    'contrat_famille_personne_date_naissance': '01/01/1976', 
    'contrat_famille_personne_nom': 'KLEIN', 
    'contrat_famille_personne_prenom': 'Madame', 
    'contrat_famille_personne_role_famille': 'C', 
    'contrat_famille_personne_roles_contrat': ['C'], 
    'contrat_produit_code': 'EMERAUDE'}, 
{'contrat_code': '1234567890', 
    'contrat_famille_code': 'FAMILLE_KLEIN', 
    'contrat_famille_personne_code': 'E1_KLEIN', 
    'contrat_famille_personne_date_naissance': '01/01/1998', 
    'contrat_famille_personne_nom': 'KLEIN', 
    'contrat_famille_personne_prenom': 'Enfant 1', 
    'contrat_famille_personne_role_famille': 'E', 
    'contrat_famille_personne_roles_contrat': ['E'], 
    'contrat_produit_code': 'EMERAUDE'}, 
{'contrat_code': '1234567890', 
    'contrat_famille_code': 'FAMILLE_KLEIN', 
    'contrat_famille_personne_code': 'E2_KLEIN', 
    'contrat_famille_personne_date_naissance': '01/01/2001', 
    'contrat_famille_personne_nom': 'KLEIN', 
    'contrat_famille_personne_prenom': 'Enfant 2', 
    'contrat_famille_personne_role_famille': 'E', 
    'contrat_famille_personne_roles_contrat': ['E'], 
    'contrat_produit_code': 'EMERAUDE'}] 
+0

Во. Впечатляет. Я должен понять, как это работает сейчас. Моя цель - получить только два уровня ключа для результата dict, т. Е. 'Contrat_code' или' personne_role_famille' и т. Д. –

+0

Черт, это работает как шарм. –

+0

Можно ли сделать то же самое и на «dicts dicts»? (не только списки dicts) –

0

Вот другое решение, которое, кажется, работает:

#!/usr/bin/env python3 

def is_list_dict(l): 
    return isinstance(l, list) and len(l) and isinstance(l[0], dict) 

def fix_list(name, lst, extra): 
    """Given a list of dictionaries, generate a list of flattened dictionaries""" 
    for d in lst: 
     if isinstance(d, dict): 
      for d1 in fix_dict(name, d): 
       d2 = d1.copy() 
       d2.update(extra) 
       yield d2 
     else: 
      yield d 

def fix_dict(name, d): 
    """ 
    Given a name and dictionary, returns a list of dictionaries. 
    """ 
    name = name.rstrip('s') 
    extra = [{ '{}_{}'.format(name, k):v for k,v in d.items() if not is_list_dict(v) }] 

    res = {} 
    for k,v in d.items(): 
     if is_list_dict(v): 
      extra2 = [] 
      for x in extra: 
       extra2.extend(fix_list(k, v, x)) 
      extra = extra2 
    return extra 

def recursive_flatten(d): 
    return list(fix_list('', [d], {})) 

from pprint import pprint 
data = { 
    'contrats': [ 
     { 
      'code': '1234567890', 
      'produits': [ 
       { 
        'code': 'MAGENTA', 
       }, 
       { 
        'code': 'EMERAUDE', 
       }, 
      ], 
      'familles': [ 
       { 
        'code': 'FAMILLE_KLEIN', 
        'personnes': [ 
         { 
          'code': 'MR_KLEIN', 
          'nom': 'KLEIN', 
          'prenom': 'Monsieur', 
          'date_naissance': '01/01/1973', 
          'role_famille': 'A', 
          'roles_contrat': ['A', 'S', 'P'], 
         }, 
         { 
          'code': 'MME_KLEIN', 
          'nom': 'KLEIN', 
          'prenom': 'Madame', 
          'date_naissance': '01/01/1976', 
          'role_famille': 'C', 
          'roles_contrat': ['C'], 
         }, 
         { 
          'code': 'E1_KLEIN', 
          'nom': 'KLEIN', 
          'prenom': 'Enfant 1', 
          'date_naissance': '01/01/1998', 
          'role_famille': 'E', 
          'roles_contrat': ['E'], 
         }, 
         { 
          'code': 'E2_KLEIN', 
          'nom': 'KLEIN', 
          'prenom': 'Enfant 2', 
          'date_naissance': '01/01/2001', 
          'role_famille': 'E', 
          'roles_contrat': ['E'], 
         }, 
        ], 
       }, 
      ], 
     }, 
    ], 
} 
pprint(recursive_flatten(data)) 

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

[{'contrat_code': '1234567890', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'MR_KLEIN', 
    'personne_date_naissance': '01/01/1973', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Monsieur', 
    'personne_role_famille': 'A', 
    'personne_roles_contrat': ['A', 'S', 'P'], 
    'produit_code': 'MAGENTA'}, 
{'contrat_code': '1234567890', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'MR_KLEIN', 
    'personne_date_naissance': '01/01/1973', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Monsieur', 
    'personne_role_famille': 'A', 
    'personne_roles_contrat': ['A', 'S', 'P'], 
    'produit_code': 'EMERAUDE'}, 
{'contrat_code': '1234567890', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'MME_KLEIN', 
    'personne_date_naissance': '01/01/1976', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Madame', 
    'personne_role_famille': 'C', 
    'personne_roles_contrat': ['C'], 
    'produit_code': 'MAGENTA'}, 
{'contrat_code': '1234567890', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'MME_KLEIN', 
    'personne_date_naissance': '01/01/1976', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Madame', 
    'personne_role_famille': 'C', 
    'personne_roles_contrat': ['C'], 
    'produit_code': 'EMERAUDE'}, 
{'contrat_code': '1234567890', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'E1_KLEIN', 
    'personne_date_naissance': '01/01/1998', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Enfant 1', 
    'personne_role_famille': 'E', 
    'personne_roles_contrat': ['E'], 
    'produit_code': 'MAGENTA'}, 
{'contrat_code': '1234567890', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'E1_KLEIN', 
    'personne_date_naissance': '01/01/1998', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Enfant 1', 
    'personne_role_famille': 'E', 
    'personne_roles_contrat': ['E'], 
    'produit_code': 'EMERAUDE'}, 
{'contrat_code': '1234567890', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'E2_KLEIN', 
    'personne_date_naissance': '01/01/2001', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Enfant 2', 
    'personne_role_famille': 'E', 
    'personne_roles_contrat': ['E'], 
    'produit_code': 'MAGENTA'}, 
{'contrat_code': '1234567890', 
    'famille_code': 'FAMILLE_KLEIN', 
    'personne_code': 'E2_KLEIN', 
    'personne_date_naissance': '01/01/2001', 
    'personne_nom': 'KLEIN', 
    'personne_prenom': 'Enfant 2', 
    'personne_role_famille': 'E', 
    'personne_roles_contrat': ['E'], 
    'produit_code': 'EMERAUDE'}] 
0

Основано на решении @ martijn-pieters с вложенными словарями. Я прав?

def recursive_flatten(idict, prefix='', long_keys=False, separator='_'): 
    """ 
    Met à plat un dictionnaire (avec listes et dictionnaires imbriqués) en renommant les clés 
    :param idict: Dictionnaire à mettre à plat 
    :param prefix: Préfixe des clés (utile pendant la récursion) 
    :param long_keys: Utilise des clés longues (avec l'historique de la hiérarchie) 
    :param separator: Séparateur entre les sections et les clés 
    :return: (Générateur) Combinaison du dictionnaire 
    """ 
    result = {} 
    nested = {} 
    dicts = [] 

    # Récupère les clés mises à plat 
    for key, value in idict.items(): 
     result_key = (prefix + separator + key.rstrip('s')).lstrip(separator) 
     if isinstance(value, list) and value and isinstance(value[0], dict): 
      # Les dictionnaire imbriqués dans des listes sont à traiter récursivement 
      nested[result_key if long_keys else key.rstrip('s')] = value 
      continue 
     elif isinstance(value, dict): 
      # Les dictionnaires imbriqués dans des dictionnaires sont récupérés immédiatement par récursivité 
      dicts += list(recursive_flatten(value, result_key, long_keys, separator)) 
      continue 
     result[result_key] = value 

    # Retourne le résultat s'il n'y a pas de clés imbriquées 
    if not nested: 
     # Ajoute les dictionnaires imbriqués 
     for d in dicts: 
      result.update(d) 
     yield result 
     return 

    # Crée les différentes combinaisons des structures imbriquées 
    for nested_combos in product(*nested.values()): 
     results = [result] 
     for nested_key, nested_value in zip(nested, nested_combos): 
      # Fusionne les données imbriquées avec les résultats 
      if isinstance(nested_value, dict): 
       results = [ 
        dict(r, **rec_result) 
        for rec_result in recursive_flatten(nested_value, nested_key, long_keys, separator) 
        for r in results 
       ] 
     for result in results: 
      # Ajoute les dictionnaires imbriqués 
      for d in dicts: 
       result.update(d) 
      yield result 

# Récupère les clés mises à plat 
for key, value in idict.items(): 
    result_key = (prefix + separator + key.rstrip('s')).lstrip(separator) 
    if isinstance(value, list) and value and isinstance(value[0], dict): 
     # Les dictionnaire imbriqués dans des listes sont à traiter récursivement 
     nested[result_key if long_keys else key.rstrip('s')] = value 
     continue 
    elif isinstance(value, dict): 
     # Les dictionnaires imbriqués dans des dictionnaires sont récupérés immédiatement par récursivité 
     dicts += list(recursive_flatten(value, result_key, long_keys, separator)) 
     continue 
    result[result_key] = value 

# Retourne le résultat s'il n'y a pas de clés imbriquées 
if not nested: 
    # Ajoute les dictionnaires imbriqués 
    for d in dicts: 
     result.update(d) 
    yield result 
    return 

# Crée les différentes combinaisons des structures imbriquées 
for nested_combos in product(*nested.values()): 
    results = [result] 
    for nested_key, nested_value in zip(nested, nested_combos): 
     # Fusionne les données imbriquées avec les résultats 
     if isinstance(nested_value, dict): 
      results = [ 
       dict(r, **rec_result) 
       for rec_result in recursive_flatten(nested_value, nested_key, long_keys, separator) 
       for r in results 
      ] 
    for result in results: 
     # Ajoute les dictionnaires imbriqués 
     for d in dicts: 
      result.update(d) 
     yield result 
Смежные вопросы