For example I have a dictionary like this:
my_dict = {
'name_1': 'method_name_x',
'name_2': 'method_name_x',
'name_3': 'method_name_y',
}
(keys and values of the dictionary are simply strings)
I want to transform this dictionary so that all values will be mapped to a list of keys which have these value.
Example result:
my_transformed_dict = {
'method_name_x': ['name_1', 'name_2'],
'method_name_y': ['name_3'],
}
I could do this by the following code:
my_transformed_dict = dict.fromkeys(my_dict.values(), [])
for k, v in my_dict.items():
my_transformed_dict[v].append(k)
But this will end up addind every key to the values somehow.
I also thought of using dict.setdefault(), like this:
my_transformed_dict = dict()
for k, v in my_dict:
my_transformed_dict.setdefault(v, []).append(k)
This works as indentend, but:
What would be best practice to solve this?
Is there a simpler way to solve this (maybe using a library)? Or just doing the code as a readable one-liner?
You can use itertools.groupby, for example:
from itertools import groupby
my_dict = {
'name_1': 'method_name_x',
'name_2': 'method_name_x',
'name_3': 'method_name_y',
}
print({k: list(v) for k, v in groupby(sorted(my_dict, key=lambda k: my_dict[k]), key=lambda k: my_dict[k])})
Output:
{'method_name_x': ['name_1', 'name_2'], 'method_name_y': ['name_3']}
Related
I have this data:
data =
{
"a":{
"00066554466":{
"Id":650,
"Passwd":"e1c2a06545de9164d7e87cd98bed57c5",
"zone":"Europe/Zurich"
},
"8745212300":{
"Id":400,
"Passwd":"ecb95502daace7f46bf12b484d086e5b",
"zone":"Europe/Zurich"
},
"8745212301":{
"Id":401,
"Passwd":"ecb95502daace7f46bf12b484d086e5b",
"zone":"Europe/Zurich"
},
"8745212302":{
"DevId":402,
"Passwd":"ecb95502daace7f46bf12b484d086e5b",
"zone":"Europe/Zurich"
}
}
}
I would like to group keys with same Passwd. So result should be like the following.
{
"e1c2a06545de9164d7e87cd98bed57c5":[
"00066554466"
],
"ecb95502daace7f46bf12b484d086e5b":[
"8745212300",
"8745212301",
"8745212302"
]
}
I tried with itertools.groupby and with for k,v in xxx, but the result is never what I need.
itertools.groupby works well when then data is already sorted with values to be grouped in a successive order, which might not always be the case with your data.
Rather use dict.setdefault and a nested loop:
out = {}
for d1 in data.values():
for k, d2 in d1.items():
out.setdefault(d2['Passwd'], []).append(k)
print(out)
Variant with a defaultdict:
from collections import defaultdict
out = defaultdict(list)
for d1 in data.values():
for k, d2 in d1.items():
out[d2['Passwd']].append(k)
print(dict(out))
Output:
{'e1c2a06545de9164d7e87cd98bed57c5': ['00066554466'],
'ecb95502daace7f46bf12b484d086e5b': ['8745212300', '8745212301', '8745212302']}
One solution, but probably not the pythonic is just to do:
passwd_group = {}
for k, val in data["a"]:
if val["Passwd"] not in passwd_group:
passwd_group[val["Passwd"]] = []
passwd_group.append(k)
This may not be ideal but got it working.
new_dict = {}
for L in data.keys():
x = data[L]
for M in x.keys():
y = x[M]
for N in y.keys():
if N == "Passwd":
new_list = new_dict.get(y[N], [])
new_list.append(M)
new_dict[y[N]] = new_list
print(new_dict)
this is what my dict looks like:
ranked_imgs = {'d57b26aa-c8f8-4e08-9e8f-8aecd6e952a5': {'small': [],
'medium': [['https://www.pablo-ruiz-picasso.net/images/works/261.jpg', 1]],
'large': []},
'b10ecfdb-f590-4930-9975-aa12dc267f2f': {'small': [],
'medium': [],
'large': [['https://www.pablo-ruiz-picasso.net/images/works/3936.jpg', 1]]},......}
This is what I'm trying to do using dict comprehension but it fails because the temporary list is over ridden due to for loop:
dct = {k:[x for x in vv] for k,v in ranked_imgs.items() for vk,vv in v.items()}
or something like this maybe :
dct = {k:sum(vv,[]) for k,v in ranked_imgs.items() for vk,vv in v.items()}
This is what the code will look like with comprehension:
# ranked_imgs_={}
# for k,v in ranked_imgs.items():
# lst = []
# for vk,vv in v.items():
# for url in vv:
# lst.append(url)
# ranked_imgs_[k] = lst
I'm curious to know if there is a pythonic way to do this using dict comprehension!
Your expected output is a dict of lists, so you can use a list comprehension as the output value of a dict comprehension. Also note that you can iterate over v.values() instead of v.items() since you aren't using the keys (thanks to #MechanicPig for pointing it out):
dct = {k: [url for vv in v.values() for url in vv] for k, v in ranked_imgs.items()}
Use chain.from_iterable may be a little better than the answer of #blhsing, it iterates by concatenating v.values():
from itertools import chain
result = {k: list(chain.from_iterable(v.values())) for k, v in ranked_imgs.items()}
I have a dictionary of key-value pairs, where the value is itself a dictionary. I would like to change the names of key values in that nested dictionary based on a predefined conversion.
I am using two lists to match up the values of the nested keys that I am trying to conver (Old_item1 should become New_item1):
comparison_list = ['Old_item1', 'Old_item2']
new_prods = ['New_item1', 'New_item2']
old_dict = {
'Company1':
{
'Old_item1':
{
'key1': val,
'key2': val
},
'Old_item2':
{
'key1': val,
'key2': val
}
}
}
I tried this:
new_dict = {}
for i in comparison_list:
for j in new_prods:
new_dict['Company1'][j] = test['Company1'][i]
I get a KeyError: KeyError: 'Company1'
The desired output for each item I add to each list is:
new_dict = {
'Company1':
{
'New_item1':
{
'key1': val # old item key, val
'key2': val # old item key, val
}
}
}
You can make a dictionary of mappings from the old to the new items, and then use it to create a new sub-dictionary for each company. This then needs to be wrapped inside an outer loop over companies (although here there is only one).
For example:
comparison_list = ['Old_item1', 'Old_item2']
new_prods = ['New_item1', 'New_item2']
old_dict = {'Company1':
{'Old_item1':
{'key1': 2,
'key2': 3},
'Old_item2':
{'key1': 4,
'key2': 5}}}
key_mappings = dict(zip(comparison_list, new_prods))
new_dict = {k: {key_mappings[k1]: v1 for k1, v1 in v.items()}
for k, v in old_dict.items()}
print(new_dict)
gives:
{'Company1': {'New_item1': {'key1': 2, 'key2': 3}, 'New_item2': {'key1': 4, 'key2': 5}}}
Here is the mappings dictionary key_mappings which we used:
{'Old_item1': 'New_item1', 'Old_item2': 'New_item2'}
The easiest way to go about this is to re-create your dictionary with a comprehension:
new_dict = {company: update_item(v) for company, v in old_dict.items()}
Then parse out update_item to it's own function. You could do this inline but it makes it difficult to understand.
conversion_lookup = {
'Old_item1': 'NewItem1',
'Old_item2': 'NewItem2',
}
def update_item(item: dict) -> dict:
return { conversion_lookup.get(k, k): v for k, v in item.items() }
new_dict = {company: update_item(v) for company, v in old_dict.items()}
For the conversion here I'm using a dictionary describing the conversion. If you need to construct this automatedly:
comparison_list = ['Old_item1', 'Old_item2']
new_prods = ['New_item1', 'New_item2']
conversion_lookup = { v: new_prods[idx] for idx, v in enumerate(comparison_list) }
The reason I like a dictionary is that you can use some_dict.get(a_value, a_default_value). Then, if your value isn't in your conversion dictionary you can fall back to the original value. That's what I'm doing here:
conversion_lookup.get(k, k)
The second k is the original item's value, which is a good thing to use if your conversion list doesn't include what you want.
One approach is to do it step by step. First create the dicts with the new names and old keys, then remove the old keys, and finally add in the new names.
# Keep the values from the old keys
values = [old_dict['Company1'][old_name] for old_name in comparison_list]
# Remove old names, and add in the
for new_key, value, old in zip(new_prods, values, comparison_list):
old_dict['Company1'].pop(old)
old_dict['Company1'][new_key] = value
I have a dictionary that looks like this:
d = {key1 : {(key2,key3) : value}, ...}
so it is a dictionary of dictionaries and in the inside dict the keys are tuples.
I would like to get a triple nested dict:
{key1 : {key2 : {key3 : value}, ...}
I know how to do it with 2 loops and a condition:
new_d = {}
for key1, inside_dict in d.items():
new_d[key1] = {}
for (key2,key3), value in inside_dict.items():
if key2 in new_d[key1].keys():
new_d[key1][key2][key3] = value
else:
new_d[key1][key2] = {key3 : value}
Edit: key2 values are not guaranteed to be unique. This is why I added the condition
It feels very unpythonic to me.
Is there a faster and/or shorter way to do this?
You could use the common trick for nesting dicts arbitrarily, using collections.defaultdict:
from collections import defaultdict
tree = lambda: defaultdict(tree)
new_d = tree()
for k1, dct in d.items():
for (k2, k3), val in dct.items():
new_d[k1][k2][k3] = val
If I understand the problem correctly, for this case you can wrap all the looping up in a dict comprehension. This assumes that your data is unique:
data = {"key1": {("key2", "key3"): "val"}}
{k: {keys[0]: {keys[1]: val}} for k,v in data.items() for keys, val in v.items()}
I have a list of dictionaries as follows
dict = {2308:[{'name':'john'},{'age':'24'},{'employed':'yes'}],3452:[{'name':'sam'},{'age':'45'},{'employed':'yes'}],1234:[{'name':'victor'},{'age':'72'},{'employed':'no'}]}
I want to filter out the above dictionary to new dictionary named new_dict whose age >30.
I tried the following. As I new to programming, could not get the logic.
new_dict =[var for var in dict if dict['age']>30]
But I know it is list of dictionaries, so is there any way I can get the new list dictionaries with age >30
You can use the following dict comprehension (assuming you store the dictionary in variable d rather than dict, which would shadow the built-in dict class):
{k: v for k, v in d.items() if any(int(s.get('age', 0)) > 30 for s in v)}
This returns:
{3452: [{'name': 'sam'}, {'age': '45'}, {'employed': 'yes'}], 1234: [{'name': 'victor'}, {'age': '72'}, {'employed': 'no'}]}
Solution
people = {
2308:[
{'name':'john'},{'age':'24'},{'employed':'yes'}
],
3452:[
{'name':'sam'},{'age':'45'},{'employed':'yes'}
],
1234:[
{'name':'victor'},{'age':'72'},{'employed':'no'}
]
}
people_over_30 = {}
for k, v in people.items():
for i in range(len(v)):
if int(v[i].get('age', 0)) > 30:
people_over_30[k] = [v]
print(people_over_30)
Output
(xenial)vash#localhost:~/python$ python3.7 quote.py
{3452: [[{'name': 'sam'}, {'age': '45'}, {'employed': 'yes'}]], 1234: [[{'name': 'victor'}, {'age': '72'}, {'employed': 'no'}]]}
Comments
Unless this is your desired structure for this particular code, I would suggest reformatting your dictionary to look like this
people = {
2308:
{'name':'john','age':'24','employed':'yes'}
,
3452:
{'name':'sam','age':'45','employed':'yes'}
,
1234:
{'name':'victor','age':'72','employed':'no'}
}
And then your work would be much simpler and able to handle with this instead
for k, v in people.items():
if int(v.get('age', 0)) > 30:
people_over_30[k] = [v]