Is there a more pythonic/compact way to create this dictionary? - python

I have a dictionary of lists of dictionaries that looks like this:
original_dict = {
1: [{'name': 'Sam'}, {'name': 'Mahmoud'}, {'name': 'Xiao'}],
2: [{'name': 'Olufemi'}, {'name': 'Kim'}, {'name': 'Rafael'}]
}
I know that the names in the lists in this dictionary are all unique. IE: the same name will not appear multiple times in this structure. I want to compile a dictionary of all sub-dictionaries, keyed by their names. I want the result to look like this:
result_dict = {
'Sam': {'name': 'Sam'},
'Mahmoud': {'name': 'Mahmoud'},
'Xiao': {'name': 'Xiao'},
'Olufemi': {'name': 'Olufemi'},
'Kim': {'name': 'Kim'},
'Rafael': {'name': 'Rafael'}
}
So far my solution looks like this:
result_dict = {}
for list_of_dicts in original_dict.values:
for curr_dict in list_of_dicts:
result_dict[curr_dict['name']] = curr_dict
But is there a more pythonic/compact way to do this? Maybe using dict comprehension?

You can use dictionary comprehension.
result = {name['name']: name for k, v in original.items() for name in v}
The inner for loop will iterate through all the key value pairs and the outer will iterate through each name in each value.

Yes, just rewrite your loops as a dict comprehension:
original = {
1: [{'name': 'Sam'}, {'name': 'Mahmoud'}, {'name': 'Xiao'}],
2: [{'name': 'Olufemi'}, {'name': 'Kim'}, {'name': 'Rafael'}]
}
result = {d['name']: d for l in original.values() for d in l}
from pprint import pprint
pprint(result)
Output:
{'Kim': {'name': 'Kim'},
'Mahmoud': {'name': 'Mahmoud'},
'Olufemi': {'name': 'Olufemi'},
'Rafael': {'name': 'Rafael'},
'Sam': {'name': 'Sam'},
'Xiao': {'name': 'Xiao'}}

Related

Adding key and value to dictionary in python based on other dictionaries

I am using for loop in python and every loop creates a dictionary. I have the below set of dictionaries created.
{'name': 'xxxx'}
{'name': 'yyyy','age':'28'}
{'name': 'zzzz','age':'27','sex':'F'}
My requirement is to compare all the dictionaries created and find out the missing key values and add the key to missing dictionaries and order every dictionary based on key. Below is the expected output
Expected output:
{'age':'','name': 'xxxx','sex':''}
{'age':'28','name': 'yyyy','sex':''}
{'age':'27','name': 'zzzz','sex':'F'}
How to achieve this in python.
If you want to modify the dicts in-place, dict.setdefault would be easy enough.
my_dicts = [
{'name': 'xxxx'},
{'name': 'yyyy','age':'28'},
{'name': 'zzzz','age':'27','sex':'F'},
]
desired_keys = ['name', 'age', 'sex']
for d in my_dicts:
for key in desired_keys:
d.setdefault(key, "")
print(my_dicts)
prints out
[
{'name': 'xxxx', 'age': '', 'sex': ''},
{'name': 'yyyy', 'age': '28', 'sex': ''},
{'name': 'zzzz', 'age': '27', 'sex': 'F'},
]
If you don't want to hard-code the desired_keys list, you can make it a set and gather it from the dicts before the loop above.
desired_keys = set()
for d in my_dicts:
desired_keys.update(set(d)) # update with keys from `d`
Another option, if you want new dicts instead of modifying them in place, is
desired_keys = ... # whichever method you like
empty_dict = dict.fromkeys(desired_keys, "")
new_dicts = [{**empty_dict, **d} for d in my_dicts]
EDIT based on comments:
This doesn't remove keys that are not there in desired keys.
This will leave only the desired keys:
desired_keys = ... # Must be a set
for d in my_dicts:
for key in desired_keys:
d.setdefault(key, "")
for key in set(d) - desired_keys:
d.pop(key)
However, at that point it might be easier to just create new dicts:
new_dicts = [
{key: d.get(value, "") for key in desired_keys}
for d in my_dicts
]
data = [{'name': 'xxxx'},
{'name': 'yyyy','age':'28'},
{'name': 'zzzz','age':'27','sex':'F'}]
First get the maximum, to get all the keys.
Then use dict.get to get default value as empty string for each of the keys, and sort the dictionary on key, you can combine List-comprehension and dict-comprehension:
allKD = max(data, key=len)
[dict(sorted({k:d.get(k, '') for k in allKD}.items(), key=lambda x:x[0])) for d in data]
OUTPUT:
[{'age': '', 'name': 'xxxx', 'sex': ''},
{'age': '28', 'name': 'yyyy', 'sex': ''},
{'age': '27', 'name': 'zzzz', 'sex': 'F'}]
One approach:
from operator import or_
from functools import reduce
lst = [{'name': 'xxxx'},
{'name': 'yyyy', 'age': '28'},
{'name': 'zzzz', 'age': '27', 'sex': 'F'}]
# find all the keys
keys = reduce(or_, map(dict.keys, lst))
# update each dictionary with the complement of the keys
for d in lst:
d.update(dict.fromkeys(keys - d.keys(), ""))
print(lst)
Output
[{'name': 'xxxx', 'age': '', 'sex': ''}, {'name': 'yyyy', 'age': '28', 'sex': ''}, {'name': 'zzzz', 'age': '27', 'sex': 'F'}]

Converting & modifying a list of NamedTuples into a List of Dict's

I need to convert a list of Named Tuples:
[Fruit(name='banana', colour='yellow', delicious=True), Fruit(name='mangosteen', colour='purple', delicious=True)]
Into a modified list of Dict's (note the extra text key):
[{'name': {'text': 'banana'}, 'colour': {'text': 'yellow'}}, {'name': {'text': 'mangosteen'}, 'colour': {'text': 'purple'}}]
I have managed to find a solution that works, but it seems ugly, I am fairly new to Python.
Fruit = namedtuple('Fruit', 'name colour')
fruitlist=[]
fruitlist.append(Fruit('banana', 'yellow'))
fruitlist.append(Fruit('mangosteen', 'purple'))
newfruitlist = []
for fruit in fruitlist:
fruitdict = dict(fruit._asdict()) #convert NamedTuple to Dict
tempdict={}
for key, value in fruitdict.items():
tempdict[key]={ 'text': value }
newfruitlist.append(tempdict)
Question: what is a better/cleaner way to do this?
Combine list and dict comprehensions:
from collections import namedtuple
Fruit = namedtuple('Fruit', 'name colour')
fruitlist=[]
fruitlist.append(Fruit('banana', 'yellow'))
fruitlist.append(Fruit('mangosteen', 'purple'))
out = [{k:{'text':v} for k, v in f._asdict().items()} for f in fruitlist]
print(out)
Prints:
[{'name': {'text': 'banana'}, 'colour': {'text': 'yellow'}}, {'name': {'text': 'mangosteen'}, 'colour': {'text': 'purple'}}]

Replace dictionaries keys with values from list

keys = ['id', 'name', 'address']
list = [{'Value': 1}, {'Value': 'Example name'}, {'VarCharValue': 'GA'}]
Looking for the most pythonic way to replace key dicts keys. I tried with for loop and list indexes but it was ugly. Excepted result:
list = [{'id': 1}, {'name': 'Example name'}, {'address': 'GA'}]
You can use a list comprehension with zip. To extract the only value in a dictionary d, you can use next(iter(d.values())) or list(d.values())[0].
K = ['id', 'name', 'address']
L = [{'Value': 1}, {'Value': 'Example name'}, {'VarCharValue': 'GA'}]
res = [{k: next(iter(v.values()))} for k, v in zip(K, L)]
[{'id': 1}, {'name': 'Example name'}, {'address': 'GA'}]
If you don't want to use iter(), you can use list(), which looks almost the same as jpp's solution
res = [{k: list(v.values())[0]} for k,v in zip(K,L)]
In this, you simply convert the dict_values object to a list, and get the first item, instead of getting the iterator and calling next on it.

How to compare two lists of dicts for multiple key, value pairs?

I have two lists of dicts, one is a modified subset of the other. I would like to get the elements of list_one that don't appear in list_two, based on two keys. Example:
list_one = [{'name': 'alf', 'age': 25},
{'name': 'alf', 'age': 50},
{'name': 'cid', 'age': 30}]
list_two = [{'name': 'alf', 'age': 25, 'hair_color': 'brown'},
{'name': 'cid', 'age': 30, 'hair_color': 'black'}]
desired_list = [{'name': 'alf', 'age': 50}]
How can I accomplish this? I have a feeling it is with some sort of list comprehension, as such:
desired_list = [x for x in list_one if x['name'] != x2['name'] and x['age'] != x2['age']
for all x2 in list_two]
I think this is easily done with two comprehensions as:
Code:
have_2 = {(d['name'], d['age']) for d in list_two}
extra = [d for d in list_one if (d['name'], d['age']) not in have_2]
This first creates a set of tuples which we already have, then checks which dicts do not match any of these existing keys.
Test Code:
list_one = [{'name': 'alf', 'age': 25},
{'name': 'alf', 'age': 50},
{'name': 'cid', 'age': 30}]
list_two = [{'name': 'alf', 'age': 25, 'hair_color': 'brown'},
{'name': 'cid', 'age': 30, 'hair_color': 'black'}]
have_2 = {(d['name'], d['age']) for d in list_two}
extra = [d for d in list_one if (d['name'], d['age']) not in have_2]
print(extra)
Results:
[{'name': 'alf', 'age': 50}]
Yet another possible solution:
>>> list(filter(lambda x: not any([set(x.items()).issubset(y.items()) for y in list_two]), list_one))
[{'age': 50, 'name': 'alf'}]
or:
>>> s2 = [set(i.items()) for i in list_two]
>>> list(filter(lambda x: not any([set(x.items()).issubset(y) for y in s2]), list_one))
[{'age': 50, 'name': 'alf'}]
The advantage of this approach is that it does not need to know the "keys" ('age' and 'name') present in both dictionary sets.
Use this:-
new_list = [i for i,j in zip(list_one,list_two) if i['name']!=j['name'] and i['age']!=j['age']]
print (new_list)
Output
[{'name': 'alf', 'age': 50}]
An efficient way would be to convert your two structures to dicts, keyed by the two values, then create the result dict:
key = lambda dct: (dct['name'], dct['age'])
d1 = { key(dct): dct for dct in list_one }
d2 = { key(dct): dct for dct in list_two }
desired_d = { k:v for k,v in d1.items() if k not in d2 }
print(desired_d)
print(desived_d.values())
diff = [
e for e in list_one
if (e['name'], e['age']) not in set((e['name'], e['age']) for e in list_two)
]
print diff

dictionary add values for the same keys

I have a list of dictionary:
[{'name':'Jay', 'value':'1'},{'name':'roc', 'value':'9'},{'name':'Jay', 'value':'7'},{'name':'roc', 'value':'2'}]
I want it to be:
[{'name':'Jay', 'value':'8'},{'name':'roc', 'value':'11'}]
I tried looping through but I am not able to find an example where I can do this. Any hint or idea will be appreciated.
You can use a defaultdict:
lst = [{'name':'Jay', 'value':'1'},{'name':'roc', 'value':'9'},{'name':'Jay', 'value':'7'},{'name':'roc', 'value':'2'}]
1) sum values for each name:
from collections import defaultdict
result = defaultdict(int)
for d in lst:
result[d['name']] += int(d['value'])
2) convert the name-value pair to a dictionary within a list:
[{'name': name, 'value': value} for name, value in result.items()]
# [{'name': 'roc', 'value': 11}, {'name': 'Jay', 'value': 8}]
Or if you want the value as str type as commented by #Kevin:
[{'name': name, 'value': str(value)} for name, value in result.items()]
​# [{'name': 'roc', 'value': '11'}, {'name': 'Jay', 'value': '8'}]
This is a good use case for itertools.groupby.
from itertools import groupby
from operator import itemgetter
orig = [{'name':'Jay', 'value':'1'},
{'name':'roc', 'value':'9'},
{'name':'Jay', 'value':'7'},
{'name':'roc', 'value':'2'}]
get_name = itemgetter('name')
result = [{'name': name, 'value': str(sum(int(d['value']) for d in dicts))}
for name, dicts in groupby(sorted(orig, key=get_name), key=get_name)]
Breaking it down:
get_name is a function that given a dictionary, returns the value of its "name" key. I.e., get_name = lambda x: x['name'].
sorted returns the list of dictionaries sorted by the value of the "name" key.
groupby returns an iterator of (name, dicts) where dicts is a list (ok, generator) of the dicts that share name as the value of the "name" key. (Grouping only occurs for consecutive items with the same key value, hence the need to sort the list in the previous step.)
The result is a list of new dictionaries using the given name and the sum of all the related "value" elements.
Similar to Psidom's answer but using collections.Counter which is the perfect candidate for accumulating integer values.
import collections
d =[{'name':'Jay', 'value':'1'},{'name':'roc', 'value':'9'},{'name':'Jay', 'value':'7'},{'name':'roc', 'value':'2'}]
c = collections.Counter()
for sd in d:
c[sd["name"]] += int(sd["value"])
Then, you need to rebuild the dicts if needed, by converting back to string.
print([{"name":n,"value":str(v)} for n,v in c.items()])
result:
[{'name': 'Jay', 'value': '8'}, {'name': 'roc', 'value': '11'}]
For the sake of completeness, without collections.defaultdict:
data = [{'name': 'Jay', 'value': '1'}, {'name': 'roc', 'value': '9'},
{'name': 'Jay', 'value': '7'}, {'name': 'roc', 'value': '2'}]
result = {}
# concetrate
for element in data:
result[element["name"]] = result.get(element["name"], 0) + int(element["value"])
# unpack
result = [{"name": element, "value": result[element]} for element in result]
# optionally, you can loop through result.items()
# you can, also, turn back result[elements] to str if needed
print(result)
# prints: [{'name': 'Jay', 'value': 8}, {'name': 'roc', 'value': 11}]
Another way to solve your question by using groupby from itertools module:
from itertools import groupby
a = [{'name':'Jay', 'value':'1'},{'name':'roc', 'value':'9'},{'name':'Jay', 'value':'7'},{'name':'roc', 'value':'2'}]
final = []
for k,v in groupby(sorted(a, key= lambda x: x["name"]), lambda x: x["name"]):
final.append({"name": k, "value": str(sum(int(j["value"]) for j in list(v)))})
print(final)
Output:
[{'name': 'Jay', 'value': '8'}, {'name': 'roc', 'value': '11'}]
ld = [{'name':'Jay', 'value':'1'},{'name':'roc', 'value':'9'},{'name':'Jay', 'value':'7'},{'name':'roc', 'value':'2'}]
tempDict = {}
finalList = []
for d in ld:
name = d['name']
value = d['value']
if name not in tempDict:
tempDict[name] = 0
tempDict[name] += int(value)
#tempDict => {'Jay': 8, 'roc': 11}
for name,value in tempDict.items():
finalList.append({'name':name,'value':value})
print(finalList)
# [{'name': 'Jay', 'value': 8}, {'name': 'roc', 'value': 11}]
Here's another way using pandas
names = [{'name':'Jay', 'value':'1'},{'name':'roc', 'value':'9'},{'name':'Jay', 'value':'7'},
{'name':'roc', 'value':'2'}]
df = pd.DataFrame(names)
df['value'] = df['value'].astype(int)
group = df.groupby('name')['value'].sum().to_dict()
result = [{'name': name, 'value': value} for name, value in group.items()]
Which outputs:
[{'value': 8, 'name': 'Jay'}, {'value': 11, 'name': 'roc'}]

Categories

Resources