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 )
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': {}}}}
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': {}}}}
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": {}
}
}
}
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": {}
}
}
}
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': {}}}
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 )
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': {}}}}
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': {}}}}
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": {}
}
}
}
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": {}
}
}
}
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': {}}}