Python: Clean way to update subdicts? - python

Let's say we have these two dictionaries:
a = {"A": "MyText", "B": {"Sub": "Hello", "NextSub": "Bye"}}
b = {"B": {"NextSub": 55}}
How to merge them together so that I get this result (such that it will work with every type of dictionary)?
ab = {"A": "MyText", "B": {"Sub": "Hello", "NextSub": 55}}
a.update(b) just replaces "B".
I want to merge some dicts because I need to handle with them all. So it's faster if I handle with one merged dict which contains the latest information of all dicts instead of handling with more dicts in a for-loop which would be slower.
Thank you!

For a generic solution, you can use recursion:
l = [[{"A": "MyText", "B": {"Sub": "Hello", "NextSub": "Bye"}},{"B": {"NextSub": 55}}], [{"a": {"a": {"a": 2, "b": "bye"}}}, {"a": {"a": {"a": "Hello"}}}]]
def update(a, b):
if len(a) == len(b):
return {c:d if not isinstance(d, dict) else {**d, **h} if c == e and all(not isinstance(i, dict) for _, i in d.items()) else update(d, h) for [c, d], [e, h] in zip(a.items(), b.items())}
return {c:d if not isinstance(d, dict) else {**d, **b[c]} if all(not isinstance(i, dict) for _, i in d.items()) else update(d, b) for c, d in a.items()}
results = [update(*i) for i in l]
Output:
[{'A': 'MyText', 'B': {'Sub': 'Hello', 'NextSub': 55}}, {'a': {'a': {'a': 'Hello', 'b': 'bye'}}}]

Related

In Python, how do I find keys in an array of dictionaries where the values are the same?

I'm using Python 3.8. I have an array of dictionaries, all of which have the same keys ...
list_of_dicts = [{"a": 1, "b": 2}, {"a": 1, "b": "zz"}, {"a": 1, "b": "2"}]
How do I return a list of keys in which all the values are identical? For example, the above would be just
["a"]
since all three dicts have "a" = 1.
Here is my additional answer of #JaonHax.
array_of_dicts = [{"a": 1, "b": 2}, {"a": 1, "b": "zz", "c": "cc"}, {"a": 1, "b": "2"}]
def get_same_vals(dicts):
keys = []
for key in dicts[0].keys():
is_same = True
for each_dict in array_of_dicts:
if not key in each_dict or each_dict[key] != dicts[0][key]:
is_same = False
if is_same:
keys.append(key)
return keys
print(get_same_vals(array_of_dicts))
As suggested in other answers, create a master dictionary that groups each key, then check their uniqueness.
# all keys are the same, so get the list
keys = array_of_dicts[0].keys()
# collapse values into a single dictionary
value_dict = {k: set(d[k] for d in array_of_dicts) for k in keys}
# get list of all single-valued keys
print([k for k, v in value_dict.items() if len(v) == 1])
If you know for certain that they all have the same keys, you can iterate through their keys and the list like so:
array_of_dicts = [{"a": 1, "b": 2}, {"a": 1, "b": "zz"}, {"a": 1, "b": "2"}]
def get_same_vals(dicts):
keys = []
for key in dicts[0].keys():
is_same = True
for each_dict in dicts:
if each_dict[key] != dicts[0][key]:
is_same = False
if is_same:
keys.append(key)
return keys
print(get_same_vals(array_of_dicts))
# Prints ['a']
I apologise for the inefficient code; I didn't spend that long coding this up.
Here's a possible solution, it will work also in case that the dictionaries are differently structured (have different / extra keys):
array_of_dicts = [{"a": 1, "b": 2}, {"a": 1, "b": "zz"}, {"a": 1, "b": "2"}]
def is_entry_in_all_dicts(key, value):
identical_entries_found = 0
for dict in array_of_dicts:
if key in dict:
if dict[key] == value:
identical_entries_found += 1
if identical_entries_found == len(array_of_dicts):
return True
return False
result = []
for dict in array_of_dicts:
for key, value in dict.items():
if is_entry_in_all_dicts(key, value):
if key not in result:
result.append(key)
print(result)
Output
['a']
If every dictionary has the same keys, you can combine the values into sets and find the sets with one element:
[list(x.keys())[0] for x in [{k:set([e[k] for e in list_of_dicts])} for k in list_of_dicts[0]] if len(list(x.values())[0]) == 1]
Output:
['a']
Append your N dictionaries into a giant dictionary, and check for keys that have N identical values:
giant_dict = collections.defaultdict(list)
for k, v in (e for d in list_of_dicts for e in d):
giant_dict[k].append(v)
for k, v in giant_dict.items():
if len(v) == len(list_of_dicts) and all(e == v[0] for e in v):
print(k)
Here's a more succinct approach
from functools import reduce
array_of_dicts = [{"a": 1, "b": 2}, {"a": 1, "c": "zz"}, {"a": 1, "d": "2"}]
result = reduce(lambda a, b: a.intersection(b),list(map(lambda x: set(x.keys()),
array_of_dicts)))
Group your values into a set, then figure out which keys have a set with length 1:
>>> from collections import defaultdict
>>> list_of_dicts = [{"a": 1, "b": 2}, {"a": 1, "b": "zz"}, {"a": 1, "b": "2"}]
>>> grouped_values = defaultdict(set)
>>> for d in list_of_dicts:
... for k,v in d.items():
... grouped_values[k].add(v)
...
>>> [k for k,v in grouped_values.items() if len(v) == 1]
['a']

Summing two dictionaries

I have two following dictionaries with same keys but different values:
dict1 = {"a": [{"b":1, "c":1, "d":[{"e":1, "f":1}]}]}
dict2 = {"a": [{"b":2, "c":2, "d":[{"e":2, "f":2}]}]}
I need to sum these dictionaries and get the following result:
res = {"a": [{"b":3, "c":3, "d":[{"e":3, "f":3}]}]}
How can I do that?
Here is recursive approach:
dict1 = {"a": [{"b":1, "c":1, "d":[{"e":1, "f":1}]}]}
dict2 = {"a": [{"b":2, "c":2, "d":[{"e":2, "f":2}]}]}
def add_objs(d1, d2):
if isinstance(d1, dict):
return {k: add_objs(d1[k], d2[k]) for k in d1}
if isinstance(d1, list):
return [add_objs(a, b) for a, b in zip(d1, d2)]
return d1+d2
add_objs(dict1, dict2)
# {'a': [{'b': 3, 'c': 3, 'd': [{'e': 3, 'f': 3}]}]}
This assumes that d1 and d2 have the exact same inner structure, all dicts have the same keys, all lists the same length, etc.

Map a function to values of specified keys in dictionary

Is there a convenient way to map a function to specified keys in a dictionary?
Ie, given
d = {"a": 1, "b": 2, "c": 3}
would like to map a function, say f, to keys "a" and "c":
{"a": f(1), "b": 2, "c": f(3)}
EDIT
Looking for methods that will not update the input dictionary.
You can use a dictionary comprehension:
output_dict = {k: f(v) for k, v in d.items()}
Note that f(v) will be evaluated (called) immediately and its return values will be stored as the dictionary's values.
If you want to store the function and call it later (with the arguments already stored) you can use functools.partial:
from functools import partial
def f(n):
print(n * 2)
d = {"a": 1, "b": 2, "c": 3}
output_dict = {k: partial(f, v) for k, v in d.items()}
output_dict['b']()
# 4
If you only want specific keys mapped you can of course not use .items and just override those keys:
d['a'] = partial(f, d['a'])
or more generalized
keys = ('a', 'c')
for key in keys:
d[key] = partial(f, d[key])

make a dict/json from string with duplicate keys Python

I have a string that could be parsed as a JSON or dict object. My string variable looks like this :
my_string_variable = """{
"a":1,
"b":{
"b1":1,
"b2":2
},
"b": {
"b1":3,
"b2":2,
"b4":8
}
}"""
When I do json.loads(my_string_variable), I have a dict but only the second value of the key "b" is kept, which is normal because a dict can't contain duplicate keys.
What would be the best way to have some sort of defaultdict like this :
result = {
"a": 1,
"b": [{"b1": 1, "b2": 2}, {"b1": 3, "b2": 2, "b4": 8}],
}
I have already looked for similar questions but they all deal with dicts or lists as an input and then create defaultdicts to handle the duplicate keys.
In my case I have a string variable and I would want to know if there is a simple way to achieve this.
something like the following can be done.
import json
def join_duplicate_keys(ordered_pairs):
d = {}
for k, v in ordered_pairs:
if k in d:
if type(d[k]) == list:
d[k].append(v)
else:
newlist = []
newlist.append(d[k])
newlist.append(v)
d[k] = newlist
else:
d[k] = v
return d
raw_post_data = '{"a":1, "b":{"b1":1,"b2":2}, "b": { "b1":3, "b2":2,"b4":8} }'
newdict = json.loads(raw_post_data, object_pairs_hook=join_duplicate_keys)
print (newdict)
Please note that above code depends on value type, if type(d[k]) == list. So if original string itself gives a list then there could be some error handling required to make the code robust.
Accepted answer is perfectly fine. I just wanted to show another approach.
So at first, you dedicate a list for values in order to easily accumulate next values. At the end, you call pop on the lists which have only one item. This means that the list doesn't have duplicate values:
import json
from collections import defaultdict
my_string_variable = '{"a":1, "b":{"b1":1,"b2":2}, "b": { "b1":3, "b2":2,"b4":8} }'
def join_duplicate_keys(ordered_pairs):
d = defaultdict(list)
for k, v in ordered_pairs:
d[k].append(v)
return {k: v.pop() if len(v) == 1 else v for k, v in d.items()}
d = json.loads(my_string_variable, object_pairs_hook=join_duplicate_keys)
print(d)
output:
{'a': 1, 'b': [{'b1': 1, 'b2': 2}, {'b1': 3, 'b2': 2, 'b4': 8}]}

Adding multiple values to an existing dictionary as SETS

I have a dictionary where I have the data already inside, i.e. keys have values and some of them have more than one value.
For example:
i = {"a": "111", "b": "222", "c": ["333", "444"]}
How can I change the type of the multiple values? I want them to be sets, not lists, such as:
i = {"a": {"111"}, "b": {"222"}, "c": {"333", "444"}}
One similar post is this one:
How to add multiple values to a dictionary key in python? [closed]
There it is explained how to add multiple elements to a dictionary, but they always seem to be lists.
How to change the type of the multiple values?
OR how to add them to the dictionary as sets, not lists?
Using a dict-comprehension makes converting an existing dict very easy:
i = {"a": "111", "b": "222", 'c': ["333", "444"]}
{k: set(v) if isinstance(v, list) else v for k, v in i.items()}
this converts all values that are lists to sets.
In a single line of code:
>>> i = {"a": "111", "b": "222", "c": ["333", "444"]}
>>> {k: set(v) for k, v in i.items()}
{'b': {'2'}, 'a': {'1'}, 'c': {'444', '333'}}
Or with a few more steps:
>>> i = {"a": "111", "b": "222", "c": ["333", "444"]}
>>> for k, v in i.items():
... i[k] = set(v)
>>> i
{'b': {'2'}, 'a': {'1'}, 'c': {'444', '333'}}
Instead of doing
my_dict['key'] = ['333', '444']
use a set literal:
my_dict['key'] = {'333', '444'}
That looks like a dict literal, but the lack of key: value like things makes it a set.

Categories

Resources