Inverting a nested dictionary tree

Question:

I have a dictionary that looks like:

normalDictionary = {'a' : {'b': {}},
                    'a1': {'b1': {'c1' : {},
                                  'd1' : {}}}}

and I want to invert it so that it looks like:

invertedDictionary = {'d1': {'b1': {'a1': {}}},
                      'c1': {'b1': {'a1': {}}},
                      'b': {'a': {}}}

What would that python function look like?

I cannot seem to get much past:

def invert_dictionary( node, leafName, indent ):

    keys = list( node.keys() )
    keys.sort()

    constructed = { leafName: {} }

    for key in keys:

        inverted = invert_dictionary( node[ key ], key, indent + 4 )


    return constructed

invertedDictionary = {}

for key in normalDictionary
    inverted = invert_dictionary( normalDictionary[ key ], key, indent = 0 )
Asked By: James Hudson

||

Answers:

This is a working solution:

def add_path(d, path):
    while path:
        k = path.pop()
        if k not in d:
            d[k] = {}
        d = d[k]


def invert_dict(d, target=None, path=None):
    if target is None:
        target = {}
    if path is None:
        path = []
    if not d:
        add_path(target, path)
    else:
        for k, v in d.items():
            invert_dict(v, target, path + [k])
    return target


print(invert_dict(normalDictionary))

This assumes your dictionary only contains dictionaries like your example, though. Not sure what the actual use case is, where you may have more mixed data types.

Result:

{'b': {'a': {}}, 'c1': {'b1': {'a1': {}}}, 'd1': {'b1': {'a1': {}}}}
Answered By: Grismar

This may not be the optimal algorithm but you can start with this. The idea is

  • we convert the dictionary into all “walk paths” from the “root” to all “leaves”
  • then we build another dictionary from such paths, in reversed order

Here is the code:

def getpaths(dictionary, pathhead):
    if not dictionary:
        return [pathhead]
    paths = []
    for key in dictionary:
        paths.extend(getpaths(dictionary[key], pathhead+[key]))
    return paths

def invert(dictionary):
    paths = getpaths(dictionary, [])
    inverted = {}
    for path in paths:
        head = inverted
        for node in path[::-1]:
            if node not in head:
                head[node] = {}
            head = head[node]
    return inverted

and this is how it works:

>>> normalDictionary
{'a': {'b': {}}, 'a1': {'b1': {'c1': {}, 'd1': {}}}}
>>> invert(normalDictionary)
{'b': {'a': {}}, 'c1': {'b1': {'a1': {}}}, 'd1': {'b1': {'a1': {}}}}
Answered By: adrtam

In technical terms, you have an n-ary tree and it sounds like you want to build root-to-leaf paths (using depth-first search), then create another n-ary tree by expanding each of those paths in reverse. Here’s one way (all_paths yields root-to-leaf paths, reverse each path, then iterate over each path plugging values into a dict in paths_to_dict):

import json

def all_paths(d, path=[]):
    if not d:
        yield path[:]

    for k, v in d.items():
        path.append(k)
        yield from all_paths(v, path)
        path.pop()

def paths_to_dict(paths):
    d = {}

    for path in paths:
        curr = d

        for node in path:
            curr[node] = curr = {}

    return d

if __name__ == "__main__":
    d = {
        'a': {
            'b': {}
        },
        'a1': {
            'b1': {
                'c1': {},
                'd1': {}
            }
        }
    }
    inverted_d = paths_to_dict([list(reversed(x)) for x in all_paths(d)])
    print(json.dumps(inverted_d, indent=2))

Output:

{
  "b": {
    "a": {}
  },
  "c1": {
    "b1": {
      "a1": {}
    }
  },
  "d1": {
    "b1": {
      "a1": {}
    }
  }
}
Answered By: ggorlen

A recursive implementation:

def asdict(xs: list) -> dict:
    return {} if len(xs) == 0 else {xs[0]: asdict(xs[1:])}

def inverted_dict_as_tuples(d: dict, stack: list):
    for k, v in d.items():
        if len(v) == 0:
            yield (k, *reversed(stack))
        else:
            yield from inverted_dict_as_tuples(v, [*stack, k])

def inverted_dict(d: dict) -> dict:
    return {x: asdict(xs) for x, *xs in inverted_dict_as_tuples(d, [])}

Usage:

>>> import json
>>> d = {"a": {"b": {}}, "a1": {"b1": {"c1": {}, "d1": {}}}}
>>> print(json.dumps(d, indent=2))
{
  "a": {
    "b": {}
  },
  "a1": {
    "b1": {
      "c1": {},
      "d1": {}
    }
  }
}

>>> d_inv = inverted_dict(d)
>>> print(json.dumps(d_inv, indent=2))
{
  "b": {
    "a": {}
  },
  "c1": {
    "b1": {
      "a1": {}
    }
  },
  "d1": {
    "b1": {
      "a1": {}
    }
  }
}
Answered By: Mateen Ulhaq

I think you want a single recursive function like this.

def reverse_dict(final_result, middle_result, normal_dictionary):
    for key, value in normal_dictionary.items():
        if len(value.keys()) == 0:
            final_result[key] = value
            middle_result.append(final_result[key])
        else:
            reverse_dict(final_result, middle_result, value)
            for item in middle_result:
                item[key] = {}
            middle_result = []
            for item in middle_result:
                middle_result.append(item[key])

Example:

test_normal_dictionary = {
    'a': {
        'b': {}
    },
    'a1': {
        'b1': {
            'c1': {},
            'd1': {}
        }
    }
}
result_dictionary = {}
print(f"Origin dict: {test_normal_dictionary}")
reverse_dict(result_dictionary, [], test_normal_dictionary)
print(f"Reversed dict: {result_dictionary}")

Output:

Origin dict: {'a': {'b': {}}, 'a1': {'b1': {'c1': {}, 'd1': {}}}}
Reversed dict: {'b': {'a': {}}, 'c1': {'b1': {}, 'a1': {}}, 'd1': {'b1': {}, 'a1': {}}}
Answered By: Xu Qiushi