I have written a function that takes a nested structure, and flattens it while also exploding any list elements.
def __flatten_dict_explode_list(d,flat_d,is_list=False, path=[]):
if not is_list:
for k, v in d.items():
if (isinstance(v, dict) and v != {}):
__flatten_dict_explode_list(d=v,flat_d=flat_d, path=path [k])
elif (isinstance(v, list) and len(v) > 0 and isinstance(v[0],dict)):
__flatten_dict_explode_list(d=v,flat_d=flat_d,is_list=True, path=path [k])
else:
if '/'.join(path) '/' k not in flat_d:
flat_d['/'.join(path) '/' k] = v
else:
for j in range(0,len(d)):
if (isinstance(d[j], dict)):
__flatten_dict_explode_list(d=d[j],flat_d=flat_d, path=path ['[' str(j) ']'])
else:
flat_d['/'.join(path) '/' '[' str(j) ']'] = d[j]
def flatten_dict_explode_list(d):
flat_d = dict()
__flatten_dict_explode_list(d,flat_d)
return flat_d
So example, with given input
d = {'A':
{
'B': 1, 'C' : [{'D':1,'E':2,'F' : {'G' : 4}},{'D':2,'E':3,'F' : {'G' : 5}},{'H': [{'J':2}]}]
}
}
It would output
d_flat = {'A/B' : 1,
'A/C/[0]/D' : 1,
'A/C/[0]/E' : 2,
'A/C/[0]/F/G' : 4,
'A/C/[1]/D' : 2,
'A/C/[1]/E' : 3,
'A/C/[1]/F/G' : 5,
'A/C/[2]/H/[0]/J' : 2
}
But, how can I write a function that reverse this operation? Taking the flat dictionary and output a nested dictionary where also the list elements are "reverse exploded"?
def unflatten_dict(flat_d):
....
return nested_dict
Appreciate any help or tips.
EDIT
I have issues with understanding how to handle the wanted list elements. Also, I am not sure if my current approach for creating the nested dictionary is best practice.
My current attempt, which does not work with list elements is.
def __unflatten_dict_help(key,val,dictionary,separator, path=[]):
parameter_split = key.split(separator)
parameter_split = [x for x in parameter_split if x != ""]
for path in parameter_split[:-1]:
if path not in dictionary:
dictionary[path] = {}
if type(dictionary[path]) == dict:
dictionary = dictionary[path]
if len(parameter_split) == 1 and parameter_split[-1] not in dictionary:
dictionary[parameter_split[-1]] = {}
else:
dictionary[parameter_split[-1]] = val
def unflatten_dict(d,separator):
output_d = dict()
for key,val in d.items():
__unflatten_dict_help(key,val,output_d,separator)
return output_d
Which outputs when unflatten_dict(flat_d,"/")
{'A':
{'B': 1, 'C':
{'[0]': {'D': 1, 'E': 2, 'F': {'G': 4}},
'[1]': {'D': 2, 'E': 3, 'F': {'G': 5}},
'[2]': {'H':
{'[0]': {'J': 2}
}
}
}
}
}
CodePudding user response:
Something like this would work as __unflatten_dict_help
:
def __unflatten_dict_help(key, val, d, sep):
parts = [p if len(p) < 2 or p[0] != '[' or p[-1] != ']' else
int(p[1:-1]) for p in key.split(separator)]
for part, next_part in zip(parts, parts[1:] [-1]):
if isinstance(part, int):
continue
if isinstance(next_part, int):
if next_part < 0:
d[part] = val
break
if part not in d:
d[part] = []
if next_part == len(d[part]):
d[part].append({})
d = d[part][next_part]
else:
if part not in d:
d[part] = {}
d = d[part]
When running:
d = {
'A': {
'B': 1,
'C': [
{
'D': 1,
'E': 2,
'F': {
'G': 4
}
},
{
'D': 2,
'E': 3,
'F': {
'G': 5
}
},
{
'H': [
{
'J': 2
}
]
}
]
}
}
fd = flatten_dict_explode_list(d)
print(fd)
rd = unflatten_dict(fd, '/')
print(rd)
print(rd == d)
The output is:
{'A/B': 1, 'A/C/[0]/D': 1, 'A/C/[0]/E': 2, 'A/C/[0]/F/G': 4, 'A/C/[1]/D': 2, 'A/C/[1]/E': 3, 'A/C/[1]/F/G': 5, 'A/C/[2]/H/[0]/J': 2}
{'A': {'B': 1, 'C': [{'D': 1, 'E': 2, 'F': {'G': 4}}, {'D': 2, 'E': 3, 'F': {'G': 5}}, {'H': [{'J': 2}]}]}}
True
Note that:
- the correct way to check for type is not
type(d) == dict
, butisinstance(d, dict)
- you could make the dunder helper function internal to the
unflatten_dict()
function, depending on whether you mind that it's accessible to others - this code assumes that the 'flattened' dictionary was created by your code, and that list indices don't skip or appear out of order
- it also assumes a list will only every contain dictionaries, and none of your dictionary values at the leaf nodes are lists