Creating a nested dictionary from a flattened dictionary

后端 未结 7 2015
-上瘾入骨i
-上瘾入骨i 2020-12-15 02:43

I have a flattened dictionary which I want to make into a nested one, of the form

flat = {\'X_a_one\': 10,
        \'X_a_two\': 20, 
        \'X_b_one\': 10,         


        
相关标签:
7条回答
  • 2020-12-15 03:13

    Here is a reasonably readable recursive result:

    def unflatten_dict(a, result=None, sep='_'):
    
        if result is None:
            result = dict()
    
        for k, v in a.items():
            k, *rest = k.split(sep, 1)
            if rest:
                unflatten_dict({rest[0]: v}, result.setdefault(k, {}), sep=sep)
            else:
                result[k] = v
    
        return result
    
    
    flat = {'X_a_one': 10,
            'X_a_two': 20,
            'X_b_one': 10,
            'X_b_two': 20,
            'Y_a_one': 10,
            'Y_a_two': 20,
            'Y_b_one': 10,
            'Y_b_two': 20}
    
    print(unflatten_dict(flat))
    {'X': {'a': {'one': 10, 'two': 20}, 'b': {'one': 10, 'two': 20}}, 
     'Y': {'a': {'one': 10, 'two': 20}, 'b': {'one': 10, 'two': 20}}}
    

    This is based on a couple of the above answers, uses no imports and is only tested in python 3.

    0 讨论(0)
  • 2020-12-15 03:16

    Here's one way using collections.defaultdict, borrowing heavily from this previous answer. There are 3 steps:

    1. Create a nested defaultdict of defaultdict objects.
    2. Iterate items in flat input dictionary.
    3. Build defaultdict result according to the structure derived from splitting keys by _, using getFromDict to iterate the result dictionary.

    This is a complete example:

    from collections import defaultdict
    from functools import reduce
    from operator import getitem
    
    def getFromDict(dataDict, mapList):
        """Iterate nested dictionary"""
        return reduce(getitem, mapList, dataDict)
    
    # instantiate nested defaultdict of defaultdicts
    tree = lambda: defaultdict(tree)
    d = tree()
    
    # iterate input dictionary
    for k, v in flat.items():
        *keys, final_key = k.split('_')
        getFromDict(d, keys)[final_key] = v
    
    {'X': {'a': {'one': 10, 'two': 20}, 'b': {'one': 10, 'two': 20}},
     'Y': {'a': {'one': 10, 'two': 20}, 'b': {'one': 10, 'two': 20}}}
    

    As a final step, you can convert your defaultdict to a regular dict, though usually this step is not necessary.

    def default_to_regular_dict(d):
        """Convert nested defaultdict to regular dict of dicts."""
        if isinstance(d, defaultdict):
            d = {k: default_to_regular_dict(v) for k, v in d.items()}
        return d
    
    # convert back to regular dict
    res = default_to_regular_dict(d)
    
    0 讨论(0)
  • 2020-12-15 03:17
    output = {}
    
    for k, v in source.items():
        # always start at the root.
        current = output
    
        # This is the part you're struggling with.
        pieces = k.split('_')
    
        # iterate from the beginning until the second to last place
        for piece in pieces[:-1]:
           if not piece in current:
              # if a dict doesn't exist at an index, then create one
              current[piece] = {}
    
           # as you walk into the structure, update your current location
           current = current[piece]
    
        # The reason you're using the second to last is because the last place
        # represents the place you're actually storing the item
        current[pieces[-1]] = v
    
    0 讨论(0)
  • 2020-12-15 03:19

    The other answers are cleaner, but since you mentioned recursion we do have other options.

    def nest(d):
        _ = {}
        for k in d:
            i = k.find('_')
            if i == -1:
                _[k] = d[k]
                continue
            s, t = k[:i], k[i+1:]
            if s in _:
                _[s][t] = d[k]
            else:
                _[s] = {t:d[k]}
        return {k:(nest(_[k]) if type(_[k])==type(d) else _[k]) for k in _}
    
    0 讨论(0)
  • 2020-12-15 03:19

    Here is my take:

    def nest_dict(flat):
        result = {}
        for k, v in flat.items():
            _nest_dict_rec(k, v, result)
        return result
    
    def _nest_dict_rec(k, v, out):
        k, *rest = k.split('_', 1)
        if rest:
            _nest_dict_rec(rest[0], v, out.setdefault(k, {}))
        else:
            out[k] = v
    
    flat = {'X_a_one': 10,
            'X_a_two': 20, 
            'X_b_one': 10,
            'X_b_two': 20, 
            'Y_a_one': 10,
            'Y_a_two': 20,
            'Y_b_one': 10,
            'Y_b_two': 20}
    nested = {'X': {'a': {'one': 10,
                          'two': 20}, 
                    'b': {'one': 10,
                          'two': 20}}, 
              'Y': {'a': {'one': 10,
                          'two': 20},
                    'b': {'one': 10,
                          'two': 20}}}
    print(nest_dict(flat) == nested)
    # True
    
    0 讨论(0)
  • 2020-12-15 03:22

    You can use itertools.groupby:

    import itertools, json
    flat = {'Y_a_two': 20, 'Y_a_one': 10, 'X_b_two': 20, 'X_b_one': 10, 'X_a_one': 10, 'X_a_two': 20, 'Y_b_two': 20, 'Y_b_one': 10}
    _flat = [[*a.split('_'), b] for a, b in flat.items()]
    def create_dict(d): 
      _d = {a:list(b) for a, b in itertools.groupby(sorted(d, key=lambda x:x[0]), key=lambda x:x[0])}
      return {a:create_dict([i[1:] for i in b]) if len(b) > 1 else b[0][-1] for a, b in _d.items()}
    
    print(json.dumps(create_dict(_flat), indent=3))
    

    Output:

    {
     "Y": {
        "b": {
          "two": 20,
          "one": 10
        },
        "a": {
          "two": 20,
          "one": 10
        }
     },
      "X": {
         "b": {
         "two": 20,
         "one": 10
       },
        "a": {
         "two": 20,
         "one": 10
       }
     }
    }
    
    0 讨论(0)
提交回复
热议问题