简体   繁体   中英

Remove extra nesting from dictionary

I have this dictionary:

mydict = {'KEY': {'KEY': {'KEY': {'KEY': ['mylist']}}}}

Is there any pythonic way to remove the nested keys and get to the last item obtaining this:

{'KEY': ['mylist']}

Without having to manually go one by one through the items like:

def simplify_dict(d: dict) -> dict:
    for k, v in d.items():
        for k1, v1 in v.items():
            for k2, v2 in v1.items():
                for k3, v3 in v2.items():
                    return {k: v3}

This is a recursive function that also does some basic checks on the input dict.

def reduce_dict(d):
    for k,v in d.items():
        if isinstance(v, dict) \
        and len(v) == 1 \
        and k in v.keys():
            return reduce_dict(v)
    return d


mydict = {'KEY': {'KEY': {'KEY': {'KEY': ['mylist']}}}}

print(reduce_dict(mydict))
#{'KEY': ['mylist']}

A recursive solution, using defaultdict to build a list of all dictionary values with the same key. I've added a few extra values to your dictionary for demonstration purposes.

>>> mydict = {'KEY': {'KEY': {'KEY': {'KEY': ['mylist'], 'name': 'foo'}, 'name': 'bar'}}}
>>> def flatten_dict(d, results=defaultdict(list)):
...     for k, v in d.items():
...         if isinstance(v, dict):
...             flatten_dict(v, results)
...         else:
...             results[k].append(v)
...     return dict(results)
...
>>> flatten_dict(mydict)
{'KEY': [['mylist']], 'name': ['foo', 'bar']}
>>>

An additional exercise would be to add a levels argument to flatten_dict to possibly restrict the number of levels it recurses.


Now, if you're just looking for the most deeply nested dictionary, we can use a different recursive function to iterate over the dictionary mydict and map a depth number to each nested dictionary. Then we just have to iterate over that list and find the dictionary or dictionaries with the biggest number.

An intermediate implementation of this idea, showing the list containing each dictionary and its depth.

>>> def deepest_dict(d, result=[], depth=0):
...     result.append((depth, d))
...     for k, v in d.items():
...         if isinstance(v, dict):
...             deepest_dict(v, result, depth+1)
...     return result
...
>>> deepest_dict(mydict)
[(0, {'KEY': {'KEY': {'KEY': {'KEY': ['mylist'], 'name': 'foo'}, 'name': 'bar'}}}), (1, {'KEY': {'KEY': {'KEY': ['mylist'], 'name': 'foo'}, 'name': 'bar'}}), (2, {'KEY': {'KEY': ['mylist'], 'name': 'foo'}, 'name': 'bar'}), (3, {'KEY': ['mylist'], 'name': 'foo'})]
>>>

And the complete implementation, just filtering out what we want from that.

>>> def deepest_dict(d, result=[], depth=0):
...     result.append((depth, d))
...     for k, v in d.items():
...         if isinstance(v, dict):
...             deepest_dict(v, result, depth+1)
...     max_depth = max(depth for depth, _ in result)
...     return [elem for depth, elem in result if depth == max_depth]
...
>>>
>>> deepest_dict(mydict)
[{'KEY': ['mylist'], 'name': 'foo'}]
>>>

You can use a recursive generator to produce the items. As your example is a bit ambiguous (non branched), I'm providing two different solutions depending on the expected output (single items or grouped items):

used input:

mydict = {'KEY': {'KEY': {'KEY': {'KEY': ['mylist'],
                                  'KEY3': 'abc',
                                  'KEY4': 'def'}},
                  'KEY2': '123'}}
single items
def unnest(d):
    for k,v in d.items():
        if isinstance(v, dict):
            for x in unnest(v):
                yield x
        else:
            yield {k:v}
            
list(unnest(mydict))
# [{'KEY': ['mylist']}, {'KEY3': 'abc'}, {'KEY4': 'def'}, {'KEY2': '123'}]
grouped items
def unnest(d):
    out = {}
    for k,v in d.items():
        if isinstance(v, dict):
            for x in unnest(v):
                yield x
        else:
            out[k] = v
    if out:
        yield out
            
list(unnest(mydict))
# [{'KEY': ['mylist'], 'KEY3': 'abc', 'KEY4': 'def'}, {'KEY2': '123'}]
as flat dictionary
{k:v for d in unnest(mydict) for k,v in d.items()}
# {'KEY': ['mylist'], 'KEY3': 'abc', 'KEY4': 'def', 'KEY2': '123'}

If you know the keys and the final value is not a dictionary:

d = {'KEY': {'KEY': {'KEY': {'KEY': ['mylist']}}}}
while isinstance(d, dict):
    d = d['KEY']
{'KEY': d}
#{'KEY': ['mylist']}

Assuming that what you are looking for is a dictionary formed of the leaf dictionaries in a hierarchy, you could do it iteratively by progressively replacing keys that contain a dictionary with the content of that dictionary:

mydict = {'KEY': 
             {'KEY': 
                {'KEY': 
                   {'KEY': ['mylist']}
                },
              'KEY2':
                   {'KEY3':[1,2,3]}
             },
          'KEY4':[7,8]
         }

while dict in map(type,mydict.values()):
    subKeys = [k for k in mydict if isinstance(mydict[k],dict)]
    mydict.update([kv for s in subKeys for kv in mydict.pop(s).items()])
    
print(mydict)
{'KEY4': [7, 8], 'KEY3': [1, 2, 3], 'KEY': ['mylist']}

If you want it as a new dictionary (instead of in-place), a recursive function is probably the most elegant way to achieve it:

def leaf(D):
    return { k:v for s,d in D.items()
             for k,v in (leaf(d).items() if isinstance(d,dict) else [(s,d)]) }

print(leaf(mydict))
{'KEY': ['mylist'], 'KEY3': [1, 2, 3], 'KEY4': [7, 8]}

If you're ok with using libraries, a ChainMap could be used to implement the recursive function:

from collections import ChainMap
def leaf(D):
    return dict(ChainMap(*(leaf(d) if type(d)==dict else {k:d}
                           for k,d in D.items())))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM