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 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': {}}}}
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": {}
}
}
}
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': {}}}}
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": {}
}
}
}
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': {}}}
Related
As json example, I have this:
{
"id": "foo",
"items": [
{
"id": "aaa",
"colour": "blue"
},
{
"id": "bbb",
"colour": "red",
}
]
}
Once json is read in Python into a dictionary:
What I want to do is a function that receives field to fill and value to be set
For example:
func(json_dict, 'items[0].colour', 'green')
or
func(json_dict, 'id', 'bar')
And the challenge is to do this for whatever json with whatever deep I have.
But I don't know how dynamically I can do:
json_dict['items'][0]['colour'] = 'green'
json_dict['id'] = 'bar'
Parse the path and define an array of keys to traverse. Then recursively find the last key and set the value.
import re
def set_val(json_obj, path, val):
path_arr = []
for _, k, i in re.findall(r'((\w+)(\[\d+\])?\.?)', path):
path_arr.append(k)
if i: path_arr.append(int(i[1:-1]))
def _get(c_path, curr=json_obj):
if len(c_path) == 1: curr[c_path[0]] = val
else: _get(c_path[1:], curr[c_path[0]])
_get(path_arr)
my_json = {'a': {'b': [1, 2, {'x': 2}]}}
set_val(my_json, 'a.b[2].x', 5)
print(my_json) # ==> {'a': {'b': [1, 2, {'x': 5}]}}
Here's one alternative to a regex approach. However, a minor disadvantage is you can't use a 'a.b[2].x' syntax anymore.
from typing import Tuple, Union
def set_val(d, path, val):
*paths, last_path = _get_dot_path(path)
path_dict = d
try:
for i, p in enumerate(paths):
path_dict = path_dict[p]
path_dict[last_path] = val
except (KeyError, IndexError, TypeError):
# TODO use variables i and p, if needed
raise
def _get_dot_path(path: str) -> Tuple[Union[str, int], ...]:
return tuple([int(s) if s.lstrip('-').isdecimal() else s
for s in path.split('.')])
my_json = {'a': {'b': [1, 2, {'x': 2}]}}
set_val(my_json, 'a.b.2.x', 5)
print(my_json) # {'a': {'b': [1, 2, {'x': 5}]}}
I receive a response I have no control over from an API. Using requests response.json() will filter out duplicate keys. So I would need to turn this response into a list where each key is an element in that list: What I get now:
{
"user": {
//...
},
"user": {
//...
},
//...
}
What I need:
{
"users": [
{
"user": {
//...
}
},
{
"user": {
//...
}
},
//...
]
}
This way JSON won't filter out any of the results, and I can loop through users.
Okay, let me have a try by method used in Python json parser allow duplicate keys
All we should do is handle the pairs_list by ourself.
from json import JSONDecoder
def parse_object_pairs(pairs):
return pairs
data = """
{"foo": {"key": 2, "key": 3}, "foo": 4, "foo": 23}
"""
decoder = JSONDecoder(object_pairs_hook=parse_object_pairs)
pairs_list = decoder.decode(data)
# the pairs_list is the real thing which we can use
aggre_key = 's'
def recusive_handle(pairs_list):
dct = {}
for k, v in pairs_list:
if v and isinstance(v, list) and isinstance(v[0], tuple):
v = recusive_handle(v)
if k + aggre_key in dct:
dct[k + aggre_key].append({k: v})
elif k in dct:
first_dict = {k: dct.pop(k)}
dct[k + aggre_key] = [first_dict, {k: v}]
else:
dct[k] = v
return dct
print(recusive_handle(pairs_list))
output:
{'foos': [{'foo': {'keys': [{'key': 2}, {'key': 3}]}}, {'foo': {'bar': 4}}, {'foo': 23}]}
I have a deeply nested dictionary (arbitrary keys and values) like:
data = {
'a': {
'path': '/a/a.txt'
},
'b': {
'b1': {
'path': '/b/b1/b1.txt'
},
'b2': {
'path': '/b/b2/b2.txt'
}
}
'c': {
'c1': {
'c12': {
'path': '/c/c1/c12/c12.txt'
}
},
'c2': {
'c22': {
'path': '/c/c1/c22/c22.txt'
}
},
'c3': {
'c32': {
'path': '/c/c1/c32/c32.txt'
}
}
}
.
.
.
}
My goal is to prepend every value in the dictionary with a specific path. So basically take in the data above, operate on it:
def prepend(value, data):
return magic
data = prepend('predir/z', data)
and have the resulting dict look like:
data = {
'a': {
'path': 'predir/z/a/a.txt'
},
'b': {
'b1': {
'path': 'predir/z/b/b1/b1.txt'
},
'b2': {
'path': 'predir/z/b/b2/b2.txt'
}
}
'c': {
'c1': {
'c12': {
'path': 'predir/z/c/c1/c12/c12.txt'
}
},
'c2': {
'c22': {
'path': 'predir/z/c/c1/c22/c22.txt'
}
},
'c3': {
'c32': {
'path': 'predir/z/c/c1/c32/c32.txt'
}
}
}
.
.
.
}
I know I can use recursion to loop through the dict like so:
def prepend(directory, config):
for k, v in config.items():
if isinstance(v, dict):
prepend(directory, v)
else:
# do something
But, I'm not able to change the values during iteration. Any and all help is greatly appreciated! Thank you!
In the else branch, just add the prefix to the existing value, and store it in the key :
def prepend(directory, config):
for k, v in config.items():
if isinstance(v, dict):
prepend(directory, v)
else:
config[k] = directory + v
return config
You have to use
else:
config[k] = directory + config[k]
or
else:
config[k] = directory + v
and it will change value directly in original data so you don't need return config.
BTW: If you want to keep original data then you would have to create new_config in function.
def prepend(directory, config):
new_config = {}
for k, v in config.items():
if isinstance(v, dict):
new_config[k] = prepend(directory, v)
else:
new_config[k] = directory + v
return new_config
new_data = prepend('predir/z', data)
print(data)
print(new_data)
How to convert below src dict (nested dict)
{
'a':{'b':1, 'c':{'d':2}},
'b':3,
'c':{'d':4, 'a':5}
}
to dst dict (not nested) below?
{
'a.b':1,
'a.c.d':2,
'b':3,
'c.d':4,
'c.a':5
}
The src dict is nested dict. And the dst dict is not nested dict.
Any easy method to do this convention?
This is python package for flatten dictionary. You can use this
https://pypi.org/project/flatten-dict/
Implementation:
from flatten_dict import flatten
nested = {'a': {'b': 1, 'c': {'d': 2}},
'b': 3,
'c': {'d': 4, 'a': 5}}
flat = flatten(nested, reducer=lambda k1, k2: k2 if k1 is None else k1 + '.' + k2)
print(flat)
# {'a.b': 1, 'a.c.d': 2, 'b': 3, 'c.d': 4, 'c.a': 5}
There are multiple ways. Here is one way to do it.
nested_dict = {
'a': {
'b': 1,
'c': {
'd': 2
}
},
'b': 3,
'c': {
'd': 4,
'a': 5
},
}
flatten_dict = {}
def flatten_the_nested(nested_dict, parent_key=''):
for key, value in nested_dict.items():
new_key = parent_key + '.' + key if parent_key is not '' else key
if isinstance(value, dict):
flatten_the_nested(value, new_key)
else:
flatten_dict[new_key] = value
return flatten_dict
print(flatten_the_nested(nested_dict, ''))
You will get the following result.
{'c.d': 4, 'c.a': 5, 'b': 3, 'a.b': 1, 'a.c.d': 2}
Or if you want to use some library then you can use https://pypi.org/project/flatten-dict/
Well, its not complicated. In just a few minutes I got the following:
def flatten(dic, prefix = ""):
if prefix is not "":
prefix = prefix + "."
result = {}
for k, v in dic.iteritems():
if isinstance(v, dict):
for k1, v1 in flatten(v, prefix + k).iteritems():
result[k1] = v1
else:
result[prefix + k] = v
return result
I have not thoroughly tested this algorithm, though.
If I have a deeply nested dict is there a built-in way to subtract/remove list of "paths" (eg: keyA.keyB.key1, keyA.keyC.key2, etc) or a the keys of a second dict from the original dict? Or maybe there is a common module which has functionality like this?
Here's a suggestion:
D = { "keyA": {
"keyB" : {
"keyC" : 42,
"keyD": 13
},
"keyE" : 55
}
}
def remove_path(dictionary, path):
for node in path[:-1]:
dictionary = dictionary[node]
del dictionary[path[-1]]
remove_path(D, ["keyA", "keyB", "keyD"])
print D # prints {'keyA': {'keyB': {'keyC': 42}, 'keyE': 55}}
You'll probably want to introduce some error checking, too.
Just in case the other answers aren't what you're looking for, here's one that subtracts one dictionary from another.
def subtract(a, b):
""" Remove the keys in b from a. """
for k in b:
if k in a:
if isinstance(b[k], dict):
subtract(a[k], b[k])
else:
del a[k]
Another solution:
d = {
'A' : {
'C' : {
'D' : {
'E' : 4,
},
'F' : 5,
},
},
'B' : 2,
}
def DeepDictDel(path, dict):
for key in path.split('.'):
owner = dict
dict = dict[key]
del owner[key]
print d # prints {'A': {'C': {'D': {'E': 4}, 'F': 5}}, 'B': 2}
DeepDictDel('A.C.D', d)
print d # prints {'A': {'C': {'F': 5}}, 'B': 2}