Subtract dict A from dict B (deep del)? - python

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}

Related

How dynamically set value to complex json content, receiving key (or key path) and value as parameters

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}]}}

Extract dictionary data which match with other dictionary

I've two dictionaries one original & the other reference. And I want to match reference dict keys with original dict keys and extract all those keys, values from original which are present in reference.
For example
original_dict = {
'a': {
'1': [1, 2, 3, 4, 5]
},
'b': {
'1': {
'A': [1, 2, 3, 4]
}
},
'c': {
'3': [1]
}
}
And a reference dictionary
reference_dict = {
'a': {
'2': [1, 2, 3]
},
'b': {
'1': {
'A': []
}
},
'c': {
'3': []
}
}
And this is the extracted dictionary.
extracted_dict = {
'b': {
'1': {
'A': [1, 2, 3, 4]
}
},
'c': {
'3': [1]
}
}
Here you might have noticed that I don't care about values of reference dict. All I want is the values of original dict.
You can use recursion to accomplish this:
def merge(value_dict, key_dict):
ret = {}
for key, subdict in key_dict.items():
if key in value_dict:
if not isinstance(value_dict[key], dict):
# we found a value so don't keep recursing
ret[key] = value_dict[key]
else:
# found another dict so merge subdicts
merged = merge(value_dict[key], subdict)
if len(merged) > 0:
ret[key] = merged
return ret
merge(original_dict, reference_dict)
I´m not sure if I understand your question 100% but this might be a solution.
we use relevant keys and put them in a list to use them on a .update() dict
new_lst = []
for i in original_dict.keys():
new_lst.append(original_dict[i])
reference_dict.clear()
for i in new_lst:
reference_dict.update(i)
print(reference_dict)

Unpack nodes of a dict for each slash in the key

I have a dict:
a = {
"group_a/category_a/metric_a": 5,
"group_a/category_a/metric_b": 4,
"group_a/category_b/metric_a": 3,
"group_a/category_b/metric_b": 2,
"group_b/category_a/metric_d": 1
}
I would like to unpack the nodes by creating a nested view, seperated by each slash, where the outcome looks as follows:
b = {
"group_a": {
"category_a": {
"metric_a": 5,
"metric_b": 4
},
"category_b": {
"metric_a": 3,
"metric_b": 2
},
"group_b": {
"category_a": {
"metric_d": 1
}
}
}
How can we go from a to b? I encountered this problem when trying to publish the dict above to Firebase as the nodes group_a/category_a/metric_a is not accepted, whereas a dict that is nested is allowed.
Loop through, split each key into a list of keys, create sub-dictionaries as required...
from pprint import pprint
a = {
"group_a/category_a/metric_a": 5,
"group_a/category_a/metric_b": 4,
"group_a/category_b/metric_a": 3,
"group_a/category_b/metric_b": 2,
"group_b/category_a/metric_d": 1
}
b = {}
for k, v in a.items():
dct = b
keys = k.split("/")
for key in keys[:-1]:
if key not in dct:
dct[key] = {}
dct = dct[key]
dct[keys[-1]] = v
pprint(b)
Gives:
{'group_a': {'category_a': {'metric_a': 5, 'metric_b': 4},
'category_b': {'metric_a': 3, 'metric_b': 2}},
'group_b': {'category_a': {'metric_d': 1}}}

Inverting a nested dictionary tree

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

How to convert python nested dict to non-nested dict?

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.

Categories

Resources