it-swarm.com.de

Reduzieren Sie verschachtelte Wörterbücher und komprimieren Sie Schlüssel

Angenommen, Sie haben ein Wörterbuch wie:

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

Wie würden Sie das abflachen in etwas wie:

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

Grundsätzlich müssen Sie wie beim Reduzieren einer verschachtelten Liste nur den zusätzlichen Arbeitsaufwand für die Iteration des Diktats nach Schlüssel/Wert, das Erstellen neuer Schlüssel für Ihr neues Wörterbuch und das Erstellen des Wörterbuchs im letzten Schritt leisten.

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}
184
Imran

Es gibt zwei wichtige Überlegungen, die das Originalplakat berücksichtigen muss:

  1. Gibt es Probleme mit der Schlüsselraumbereinigung? Zum Beispiel würde {'a_b':{'c':1}, 'a':{'b_c':2}} Zu {'a_b_c':???} Führen. Die folgende Lösung umgeht das Problem, indem sie eine Iterationsfolge von Paaren zurückgibt.
  2. Wenn die Leistung ein Problem darstellt, muss für die Funktion zur Reduzierung von Schlüsseln (die ich hier als "Verbinden" bezeichne) auf den gesamten Schlüsselpfad zugegriffen werden, oder kann sie nur O(1) für jeden Knoten in ausführen der Baum? Wenn Sie in der Lage sein möchten, joinedKey = '_'.join(*keys) zu sagen, kostet Sie dies O (N ^ 2) Laufzeit. Wenn Sie jedoch bereit sind, nextKey = previousKey+'_'+thisKey Zu sagen, haben Sie O(N) Zeit. Mit der folgenden Lösung können Sie beides tun (da Sie lediglich alle Schlüssel verketten und anschließend nachbearbeiten können).

(Leistung ist wahrscheinlich kein Problem, aber ich werde auf den zweiten Punkt näher eingehen, falls es jemanden interessiert: Bei der Implementierung stehen zahlreiche gefährliche Optionen zur Auswahl. Wenn Sie dies rekursiv tun und Rendite und Re-Rendite erzielen oder alles Äquivalent, das Knoten mehr als einmal berührt (was ziemlich einfach aus Versehen zu tun ist), Sie tun möglicherweise O (N ^ 2) Arbeit eher als O (N). Dies liegt daran, dass Sie möglicherweise einen Schlüssel a, dann a_1, Dann a_1_i ... und dann a, dann a_1 Berechnen a_1_ii ..., aber Sie sollten wirklich nicht noch einmal a_1 Berechnen müssen. Auch wenn Sie es nicht neu berechnen, ist es genauso schlecht, es neu zu berechnen (ein "Level-by-Level" -Ansatz). Ein gutes Beispiel ist, über die Leistung von {1:{1:{1:{1:...(N times)...{1:SOME_LARGE_DICTIONARY_OF_SIZE_N}...}}}}) nachzudenken.

Unten ist eine Funktion, die ich geschrieben habe flattenDict(d, join=..., lift=...), die für viele Zwecke angepasst werden kann und tun kann, was Sie wollen. Leider ist es ziemlich schwierig, eine Lazy-Version dieser Funktion zu erstellen, ohne die oben genannten Performance-Einbußen zu erleiden (viele python eingebaute Funktionen wie chain.from_iterable sind nicht wirklich effizient, was ich erst nach ausgiebigen Tests von drei verschiedenen Versionen realisiert habe Code, bevor Sie sich für diesen entscheiden).

from collections import Mapping
from itertools import chain
from operator import add

_FLAG_FIRST = object()

def flattenDict(d, join=add, lift=lambda x:x):
    results = []
    def visit(subdict, results, partialKey):
        for k,v in subdict.items():
            newKey = lift(k) if partialKey==_FLAG_FIRST else join(partialKey,lift(k))
            if isinstance(v,Mapping):
                visit(v, results, newKey)
            else:
                results.append((newKey,v))
    visit(d, results, _FLAG_FIRST)
    return results

Um besser zu verstehen, was los ist, ist unten ein Diagramm für diejenigen, die mit reduce (links) nicht vertraut sind, auch bekannt als "nach links falten". Manchmal wird es mit einem Anfangswert anstelle von k0 gezeichnet (nicht Teil der Liste, die an die Funktion übergeben wird). Hier ist J unsere join Funktion. Wir verarbeiten jedes k vorn mit lift(k).

               [k0,k1,...,kN].foldleft(J)
                           /    \
                         ...    kN
                         /
       J(k0,J(k1,J(k2,k3)))
                       /  \
                      /    \
           J(J(k0,k1),k2)   k3
                    /   \
                   /     \
             J(k0,k1)    k2
                 /  \
                /    \
               k0     k1

Dies ist in der Tat dasselbe wie functools.reduce, Aber wo unsere Funktion dies für alle Schlüsselpfade des Baums tut.

>>> reduce(lambda a,b:(a,b), range(5))
((((0, 1), 2), 3), 4)

Demonstration (die ich sonst in docstring gestellt hätte):

>>> testData = {
        'a':1,
        'b':2,
        'c':{
            'aa':11,
            'bb':22,
            'cc':{
                'aaa':111
            }
        }
    }
from pprint import pprint as pp

>>> pp(dict( flattenDict(testData, lift=lambda x:(x,)) ))
{('a',): 1,
 ('b',): 2,
 ('c', 'aa'): 11,
 ('c', 'bb'): 22,
 ('c', 'cc', 'aaa'): 111}

>>> pp(dict( flattenDict(testData, join=lambda a,b:a+'_'+b) ))
{'a': 1, 'b': 2, 'c_aa': 11, 'c_bb': 22, 'c_cc_aaa': 111}    

>>> pp(dict( (v,k) for k,v in flattenDict(testData, lift=hash, join=lambda a,b:hash((a,b))) ))
{1: 12416037344,
 2: 12544037731,
 11: 5470935132935744593,
 22: 4885734186131977315,
 111: 3461911260025554326}

Performance:

from functools import reduce
def makeEvilDict(n):
    return reduce(lambda acc,x:{x:acc}, [{i:0 for i in range(n)}]+range(n))

import timeit
def time(runnable):
    t0 = timeit.default_timer()
    _ = runnable()
    t1 = timeit.default_timer()
    print('took {:.2f} seconds'.format(t1-t0))

>>> pp(makeEvilDict(8))
{7: {6: {5: {4: {3: {2: {1: {0: {0: 0,
                                 1: 0,
                                 2: 0,
                                 3: 0,
                                 4: 0,
                                 5: 0,
                                 6: 0,
                                 7: 0}}}}}}}}}

import sys
sys.setrecursionlimit(1000000)

forget = lambda a,b:''

>>> time(lambda: dict(flattenDict(makeEvilDict(10000), join=forget)) )
took 0.10 seconds
>>> time(lambda: dict(flattenDict(makeEvilDict(100000), join=forget)) )
[1]    12569 segmentation fault  python

... seufz, denke nicht, dass einer meine Schuld ist ...


[unwichtiger historischer Hinweis aufgrund von Moderationsproblemen]

In Bezug auf das mutmaßliche Duplikat von Reduzieren Sie ein Wörterbuch mit zwei Listenebenen in Python :

Die Lösung dieser Frage kann in Bezug auf diese Frage implementiert werden, indem sorted( sum(flatten(...),[]) ) ausgeführt wird. Das Gegenteil ist nicht möglich: Zwar können die Werte von flatten(...) aus dem mutmaßlichen Duplikat wiederhergestellt werden, indem ein höherer Wert zugeordnet wird -Ordner Akku, die Schlüssel kann man nicht retten. (Bearbeiten: Es stellt sich auch heraus, dass die Frage des mutmaßlichen doppelten Besitzers völlig anders ist, da sie nur Wörterbücher mit einer Tiefe von genau 2 Ebenen behandelt, obwohl eine der Antworten auf dieser Seite eine allgemeine Lösung liefert.)

58
ninjagecko

Oder wenn Sie bereits Pandas verwenden, können Sie dies mit json_normalize() wie folgt tun:

import pandas as pd

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

df = pd.io.json.json_normalize(d, sep='_')

print(df.to_dict(orient='records')[0])

Ausgabe:

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

Hier ist eine Art "funktionale", "einzeilige" Implementierung. Es ist rekursiv und basiert auf einem bedingten Ausdruck und einem diktierten Verständnis.

def flatten_dict(dd, separator='_', prefix=''):
    return { prefix + separator + k if prefix else k : v
             for kk, vv in dd.items()
             for k, v in flatten_dict(vv, separator, kk).items()
             } if isinstance(dd, dict) else { prefix : dd }

Prüfung:

In [2]: flatten_dict({'abc':123, 'hgf':{'gh':432, 'yu':433}, 'gfd':902, 'xzxzxz':{"432":{'0b0b0b':231}, "43234":1321}}, '.')
Out[2]: 
{'abc': 123,
 'gfd': 902,
 'hgf.gh': 432,
 'hgf.yu': 433,
 'xzxzxz.432.0b0b0b': 231,
 'xzxzxz.43234': 1321}
23
dividebyzero

Wenn Sie pandas verwenden, ist in pandas.io.json.normalize Eine Funktion namens nested_to_record Verborgen, die dies genau tut.

from pandas.io.json.normalize import nested_to_record    

flat = nested_to_record(my_dict, sep='_')
12
Aaron N. Brock

Code:

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

def parse_dict(init, lkey=''):
    ret = {}
    for rkey,val in init.items():
        key = lkey+rkey
        if isinstance(val, dict):
            ret.update(parse_dict(val, key+'_'))
        else:
            ret[key] = val
    return ret

print(parse_dict(test,''))

Ergebnisse:

$ python test.py
{'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}

Ich verwende Python3.2, Update für Ihre Version von Python.

12

Dies ist nicht auf Wörterbücher beschränkt, sondern auf jeden Zuordnungstyp, der .items () implementiert. Weiter ist es schneller, da es eine if-Bedingung vermeidet. Trotzdem gehen die Credits an 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)

Meine Python 3.3 Lösung mit Generatoren:

def flattenit(pyobj, keystring=''):
   if type(pyobj) is dict:
     if (type(pyobj) is dict):
         keystring = keystring + "_" if keystring else keystring
         for k in pyobj:
             yield from flattenit(pyobj[k], keystring + k)
     Elif (type(pyobj) is list):
         for lelm in pyobj:
             yield from flatten(lelm, keystring)
   else:
      yield keystring, pyobj

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

#your flattened dictionary object
flattened={k:v for k,v in flattenit(my_obj)}
print(flattened)

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

Einfache Funktion zum Reduzieren verschachtelter Wörterbücher. Ersetzen Sie für Python 3 .iteritems() durch .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

Die Idee/Anforderung war: Flache Wörterbücher ohne Elternschlüssel.

Anwendungsbeispiel:

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}

Das Verwalten von Elternschlüsseln ist ebenfalls einfach.

4
Ivy Growing

Wie wäre es mit einer funktionalen und performanten Lösung in 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(), 
        {}
    ))

Das ist noch performanter:

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(), 
        {}
    ))

In Benutzung:

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}
4
Rotareti

Dies ähnelt sowohl der Antwort von Imran als auch von Ralu. Es wird kein Generator verwendet, sondern eine Rekursion mit einem Abschluss:

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}
3
Jonathan Drake

Davouds Lösung ist sehr gut, liefert aber keine zufriedenstellenden Ergebnisse, wenn das verschachtelte Diktat auch Diktatlisten enthält, aber sein Code muss für diesen Fall angepasst werden:

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)
3
user3830731
def flatten(unflattened_dict, separator='_'):
    flattened_dict = {}

    for k, v in unflattened_dict.items():
        if isinstance(v, dict):
            sub_flattened_dict = flatten(v, separator)
            for k2, v2 in sub_flattened_dict.items():
                flattened_dict[k + separator + k2] = v2
        else:
            flattened_dict[k] = v

    return flattened_dict
2
Pari Rajaram

Wenn Sie ein verschachteltes Wörterbuch erstellen und alle eindeutigen Schlüssel anzeigen möchten, ist dies die Lösung:

def flat_dict_return_unique_key(data, unique_keys=set()):
    if isinstance(data, dict):
        [unique_keys.add(i) for i in data.keys()]
        for each_v in data.values():
            if isinstance(each_v, dict):
                flat_dict_return_unique_key(each_v, unique_keys)
    return list(set(unique_keys))
2
Ranvijay Sachan

Die obigen Antworten funktionieren wirklich gut. Ich dachte nur, ich würde die Unflatten-Funktion hinzufügen, die ich geschrieben habe:

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

Hinweis: Dies berücksichtigt nicht '_', das bereits in Schlüsseln vorhanden ist, ähnlich wie bei den flachen Gegenstücken.

2
tarequeh

Hier ist ein Algorithmus für das elegante Ersetzen vor Ort. Getestet mit Python 2.7 und Python 3.5. Verwenden des Punktzeichens als Trennzeichen.

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

Beispiel:

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

Ausgabe:

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

Ich habe diesen Code veröffentlicht hier zusammen mit dem passenden unflatten_json Funktion.

2

Verwendung von Generatoren:

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}
1
Luka Rahne

Verwenden von dict.popitem () in einer einfachen Rekursion nach Art einer verschachtelten Liste:

def flatten(d):
    if d == {}:
        return d
    else:
        k,v = d.popitem()
        if (dict != type(v)):
            return {k:v, **flatten(d)}
        else:
            flat_kv = flatten(v)
            for k1 in list(flat_kv.keys()):
                flat_kv[k + '_' + k1] = flat_kv[k1]
                del flat_kv[k1]
            return {**flat_kv, **flatten(d)}
1
FredAKA

Ich bevorzuge immer den Zugriff auf dict Objekte über .items(), daher verwende ich zum Reduzieren von Dikten den folgenden rekursiven Generator flat_items(d). Wenn Sie wieder dict haben möchten, schließen Sie es einfach wie folgt um: flat = dict(flat_items(d))

def flat_items(d, key_separator='.'):
    """
    Flattens the dictionary containing other dictionaries like here: https://stackoverflow.com/questions/6027558/flatten-nested-python-dictionaries-compressing-keys

    >>> example = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}
    >>> flat = dict(flat_items(example, key_separator='_'))
    >>> assert flat['c_b_y'] == 10
    """
    for k, v in d.items():
        if type(v) is dict:
            for k1, v1 in flat_items(v, key_separator=key_separator):
                yield key_separator.join((k, k1)), v1
        else:
            yield k, v
0