élément - trier un dictionnaire python




Aplatir les dictionnaires Python imbriqués, en compressant les clés (10)

Supposons que vous avez un dictionnaire comme:

{'a': 1,
 'c': {'a': 2,
       'b': {'x': 5,
             'y' : 10}},
 'd': [1, 2, 3]}

Comment voulez-vous l'aplatir en quelque chose comme:

{'a': 1,
 'c_a': 2,
 'c_b_x': 5,
 'c_b_y': 10,
 'd': [1, 2, 3]}

https://code.i-harness.com


Ceci est similaire à la réponse d'Imran et de Ralu. Il n'utilise pas de générateur, mais utilise plutôt la récursivité avec une fermeture:

def flatten_dict(d, separator='_'):
  final = {}
  def _flatten_dict(obj, parent_keys=[]):
    for k, v in obj.iteritems():
      if isinstance(v, dict):
        _flatten_dict(v, parent_keys + [k])
      else:
        key = separator.join(parent_keys + [k])
        final[key] = v
  _flatten_dict(d)
  return final

>>> print flatten_dict({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]})
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}

Ceci n'est pas limité aux dictionnaires, mais à tous les types de mappage. En outre, il est plus rapide car il évite une condition if. Néanmoins, les crédits vont à Imran:

def flatten(d, parent_key=''):
    items = []
    for k, v in d.items():
        try:
            items.extend(flatten(v, '%s%s_' % (parent_key, k)).items())
        except AttributeError:
            items.append(('%s%s' % (parent_key, k), v))
    return dict(items)

Fonction simple pour aplatir les dictionnaires imbriqués. Pour Python 3, remplacez .iteritems() par .items()

def flatten_dict(init_dict):
    res_dict = {}
    if type(init_dict) is not dict:
        return res_dict

    for k, v in init_dict.iteritems():
        if type(v) == dict:
            res_dict.update(flatten_dict(v))
        else:
            res_dict[k] = v

    return res_dict

L'idée / l'exigence était la suivante: Obtenez des dictionnaires plats sans clés parentales.

Exemple d'utilisation:

dd = {'a': 3, 
      'b': {'c': 4, 'd': 5}, 
      'e': {'f': 
                 {'g': 1, 'h': 2}
           }, 
      'i': 9,
     }

flatten_dict(dd)

>> {'a': 3, 'c': 4, 'd': 5, 'g': 1, 'h': 2, 'i': 9}

Garder les clés parentales est aussi simple.


Fondamentalement, de la même façon que vous avez aplatir une liste imbriquée, vous devez simplement faire un travail supplémentaire pour l'itération de la dict par clé / valeur, en créant de nouvelles clés pour votre nouveau dictionnaire et en créant le dictionnaire à la dernière étape.

import collections

def flatten(d, parent_key='', sep='_'):
    items = []
    for k, v in d.items():
        new_key = parent_key + sep + k if parent_key else k
        if isinstance(v, collections.MutableMapping):
            items.extend(flatten(v, new_key, sep=sep).items())
        else:
            items.append((new_key, v))
    return dict(items)

>>> flatten({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]})
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}

La solution de Davoud est très belle mais ne donne pas de résultats satisfaisants lorsque la dict imbriquée contient aussi des listes de dicts, mais son code est adapté pour ce cas:

def flatten_dict(d):
    items = []
    for k, v in d.items():
        try:
            if (type(v)==type([])): 
                for l in v: items.extend(flatten_dict(l).items())
            else: 
                items.extend(flatten_dict(v).items())
        except AttributeError:
            items.append((k, v))
    return dict(items)

Les réponses ci-dessus fonctionnent très bien. Je pensais juste ajouter la fonction non flattée que j'ai écrite:

def unflatten(d):
    ud = {}
    for k, v in d.items():
        context = ud
        for sub_key in k.split('_')[:-1]:
            if sub_key not in context:
                context[sub_key] = {}
            context = context[sub_key]
        context[k.split('_')[-1]] = v
    return ud

Remarque: Cela ne tient pas compte du fait que '_' est déjà présent dans les clés, tout comme les équivalents aplatis.


Ou si vous utilisez déjà des pandas, vous pouvez le faire avec json_normalize() comme ceci:

import pandas as pd

df = pd.io.json.json_normalize({'a': 1,
                                 'c': {'a': 2,
                                       'b': {'x': 5,
                                             'y' : 10}},
                                 'd': [1, 2, 3]})
print(df.to_dict(orient='records')[0])

Sortie:

{'a': 1, 'c.a': 2, 'c.b.x': 5, 'c.b.y': 10, 'd': [1, 2, 3]}

Si tu ne veux pas . séparateur, vous pouvez le pirater comme ça:

d = df.to_dict(orient='records')[0]
print dict(zip([x.replace('.', '_') for x in d.keys()], d.values()))

Sortie:

{'a': 1, 'c_a': 2, 'c_b_x': 5, 'c_b_y': 10, 'd': [1, 2, 3]}

Que diriez-vous d'une solution fonctionnelle et performante dans Python3.5?

from functools import reduce


def _reducer(items, key, val, pref):
    if isinstance(val, dict):
        return {**items, **flatten(val, pref + key)}
    else:
        return {**items, pref + key: val}

def flatten(d, pref=''):
    return(reduce(
        lambda new_d, kv: _reducer(new_d, *kv, pref), 
        d.items(), 
        {}
    ))

C'est encore plus performant:

def flatten(d, pref=''):
    return(reduce(
        lambda new_d, kv: \
            isinstance(kv[1], dict) and \
            {**new_d, **flatten(kv[1], pref + kv[0])} or \
            {**new_d, pref + kv[0]: kv[1]}, 
        d.items(), 
        {}
    ))

Utilisé:

my_obj = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y': 10}}, 'd': [1, 2, 3]}

print(flatten(my_obj)) 
# {'d': [1, 2, 3], 'cby': 10, 'cbx': 5, 'ca': 2, 'a': 1}

Utilisation de générateurs:

def flat_dic_helper(prepand,d):
    if len(prepand) > 0:
        prepand = prepand + "_"
    for k in d:
        i=d[k]
        if type(i).__name__=='dict':
            r = flat_dic_helper(prepand+k,i)
            for j in r:
                yield j
        else:
            yield (prepand+k,i)

def flat_dic(d): return dict(flat_dic_helper("",d))

d={'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}
print(flat_dic(d))


>> {'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}

Voici un algorithme pour un remplacement élégant et sur place. Testé avec Python 2.7 et Python 3.5. Utiliser le caractère point comme séparateur.

def flatten_json(json):
    if type(json) == dict:
        for k, v in list(json.items()):
            if type(v) == dict:
                flatten_json(v)
                json.pop(k)
                for k2, v2 in v.items():
                    json[k+"."+k2] = v2

Exemple:

d = {'a': {'b': 'c'}}                   
flatten_json(d)
print(d)
unflatten_json(d)
print(d)

Sortie:

{'a.b': 'c'}
{'a': {'b': 'c'}}

J'ai publié ce code here avec la fonction correspondante unflatten_json .





dictionary