Python Dict Comprehension to Create and Update Dictionary - python

I have a list of dictionaries (data) and want to convert it into dictionary (x) as below.
I am using following ‘for loop’ to achieve.
data = [{'Dept': '0123', 'Name': 'Tom'},
{'Dept': '0123', 'Name': 'Cheryl'},
{'Dept': '0123', 'Name': 'Raj'},
{'Dept': '0999', 'Name': 'Tina'}]
x = {}
for i in data:
if i['Dept'] in x:
x[i['Dept']].append(i['Name'])
else:
x[i['Dept']] = [i['Name']]
Output:
x -> {'0999': ['Tina'], '0123': ['Tom', 'Cheryl', 'Raj']}
Is it possible to implement the above logic in dict comprehension or any other more pythonic way?

The dict comprehension, even though not impossible, might not be the best choice. May I suggest using a defaultdict:
from collections import defaultdict
dic = defaultdict(list)
for i in data:
dic[i['Dept']].append(i['Name'])

It seems way too complicated to be allowed into any code that matters even the least bit, but just for fun, here you go:
{
dept: [item['Name'] for item in data if item['Dept'] == dept]
for dept in {item['Dept'] for item in data}
}

Related

Adding multiple new key-value pairs to a nested dictionary

I am new to python and I am trying to add key-value pairs to a nested dictionary. I tried a few approaches but I am not able to do it properly. This is the code that I have written:
dict1 = {}
dict1['name'] = {'domain': "something"}
i=0
while i < 10:
new = 'route'+str(i)
dict1['name']['routes'] = {new: {'bit_value': i}}
i = i+1
print(dict1)
This is the output I am getting:
{'name': {'domain': 'something', 'routes': {'route9': {'bit_value': 9}}}}
This is the kind of output that I want:
{'name': {'domain': 'something', 'routes': {'route1': {'bit_value': 1}, 'route2': {'bit_value': 2}, 'route3': {'bit_value': 3}, 'route4': {'bit_value': 4} ...}}}
I tried using this version as well, but it throws a Keyerror:
import collections
dict1 = collections.defaultdict(dict)
dict1['name'] = {'domain': "something"}
i = 0
while i < 10:
new = 'route'+str(i)
dict1['name']['routes'][new] = {'bit_value': i}
i = i+1
print(dict1)
Error message:
dict1['name']['routes'][new] = {'bit_value': i}
KeyError: 'routes'
It would be nice if this problem can be solved without changing into a for loop as the while loop will make it easier for me to integrate this with rest of the code that I have written. I would really appreciate if someone can help me.
First create the routes sub-dictionary, then insert items.
import collections
dict1 = collections.defaultdict(dict)
dict1['name'] = {'domain': "something"}
i = 0
##########
dict1['name']['routes'] = {}
##########
while i < 10:
new = 'route'+str(i)
dict1['name']['routes'][new] = {'bit_value': i}
i = i+1
print(dict1)
In your original solution, dict1['name']['routes'] is replaced with a new dictionary with only 1 key every time.
In your alternative version, dict1 is a defaultdict. So you can access e.g. dict1['randomkey'] without initializing it, but dict1['name'] or dict1['name']['routes'] is not a defaultdict. defaultdict(dict) means the values are normal dictionaries. If you need the values to be defaultdict as well, you can do defaultdict(defaultdict(dict))
But that's not what you want. You don't actually need to deal with default key values. You only need to initialize dict1['name']['routes'] once to a normal dictionary:
dict1 = {'name': {'domain': 'something', 'routes': {}}}
for i in range(10):
dict1['name']['routes'][f'route{i}'] = {'bit_value': i}
A few bonus tips
Don't mix 'string' with "string"
Use for i in range(10) instead of a while loop. Much simpler and easier to read
Use f-string to format string f'route{i}'
As a matter of fact, your code can also be achieved with dictionary comprehension
dict1 = {'name': {'domain': 'something'}}
dict1['name']['routes'] = {f'route{i}': {'bit_value': i} for i in range(10)}

convert list of dict into dict python - pythonic way

input:
list = [{'key': '1234', 'value': 100.00}, {'key': '2345', 'value': 200.0}]
output:
{'1234': 100.00, '2345': 200.0}
I now this can be achieved by looping over. Is there any pythonic way to do the same?
You're right that a loop would be straightforward.
result = {}
for curr in lst:
result[curr['key']] = result[curr['value']]
We can shorten it a bit with a dictionary comprehension. This says the same thing but more compactly
result = { curr['key']: curr['value'] for curr in lst }
By "pythonic", I take that to mean using things like dict comprehension:
list = [{'key': '1234', 'value': 100.00}, {'key': '2345', 'value': 200.0}]
output = {item["key"]: item["value"] for item in list}
print(output) # {'1234': 100.00, '2345': 200.0}
By the way, you might want to rename list to something else, because list is an internal python function.
Just for funsies, a method that involves executing no Python-level code per-item:
from operator import itemgetter
lst = [{'key': '1234', 'value': 100.00}, {'key': '2345', 'value': 200.0}]
dct = dict(map(itemgetter('key', 'value'), lst))
The itemgetter, when passed a dict from lst, extracts the key and value and returns them as a tuple. map ensures this is done for each dict in lst, and the dict constructor knows how to turn an iterable of two-tuples into a new dict, all without executing a single byte code for any given item (all bytecode involved is fixed overhead to set everything up, then the map iterator is run to exhaustion to populate the dict without returning control to the byte code eval loop, as long as all keys and values involved are built-ins implemented in C).
Just use the values of each dict to make the new dict:
dict(map(dict.values, lst))
{'1234': 100.0, '2345': 200.0}
Caution the dicts need to be in key/value order like in your example for this to work properly. Which also means this will work fine in Python 3 but you will need to use an collections.OrderedDict in python 2.7
It can be writed in one line like this with nested comprehension:
data = [{'key': '1234', 'value': 100.00}, {'key': '2345', 'value': 200.0}]
new_dict = {r[0]: r[1] for r in [list(d.values()) for d in data]}
Thanks for ShadowRanger it can be simplified to:
new_dict = dict(d.values() for d in data)
It works only with Python 3.7+ with ordered dicts.

Easier way to transform multiple lists into a dictionary?

Right now, I have this very clunky dictionary:
input_data = {'names': 'Elizabeth,Emily,Grant', 'titles': 'Sales,Accounting,Operations'}
nameList = input_data['names'].split(',')
titleList = input_data['titles'].split(',')
d1 = dict()
d2 = dict()
for val in nameList:
d1.setdefault('Name', []).append(val)
for val in titleList:
d2.setdefault('Title', []).append(val)
fullDict = dict(d1, **d2)
I'm convinced there's a better way to covert this:
{'names': 'Elizabeth,Emily,Grant', 'titles': 'Sales,Accounting,Operations'}
Into this:
{'Name': ['Elizabeth', 'Emily', 'Grant'],
'Title': ['Sales', 'Accounting', 'Operations']}
This simple dictionary comprehension seemed to produce the result you wanted.
{x: y.split(',') for x, y in input_data.items()}
If the name part is also important, then I think this should work.
{x.title()[:-1] if x[-1] == 's' else x.title(): y.split(',') for x, y in input_data.items()}
So you want to accomplish two things: Split the values in the original dictionary, and rename the keys in the original dictionary. You can do everything in-place, as opposed to creating new dictionaries.
input_data = {'names': 'Elizabeth,Emily,Grant', 'titles': 'Sales,Accounting,Operations'}
input_data['Name'] = input_data['names'].split(',')
input_data['Title'] = input_data['titles'].split(',')
del input_data['names'], input_data['titles']
print(input_data)
Returns the output:
{'Name': ['Elizabeth', 'Emily', 'Grant'], 'Title': ['Sales', 'Accounting', 'Operations']}

Making dictionary from two lists

I have two lists
l1 = ['cat','dog']
l2= [1,2]
Now I want to make a dictionary like this:
dict { {'name':cat,'id'=1}{'name':dog,'id'=2}}
I am using zip but that's not fulfilling my requirement.
result = [{'name': name, 'id': id} for (name, id) in zip(l1, l2)]
It doesn't make sense for the container all the individual dicts are in to be a dict as well (unless you want to key it on, say, id).
If you have a lot of keys and you don't want to create dict comprehension and declare what goes where.
l1 = ['cat','dog']
l2= [1,2]
[dict(zip(['name', 'id'], el)) for el in zip(l1,l2)]
Output:
[{'id': 1, 'name': 'cat'}, {'id': 2, 'name': 'dog'}]

What is the Pythonic way to iterate over a dict of dicts and lists?

I have a dict which contains some lists and some dicts, as illustrated below.
What is the most pythonic way to iterate over the dict and print out the name and address pairs for each top level dict key?
Thanks
{
'Resent-Bcc': [],
'Delivered-To': [],
'From': {'Name': 'Steve Watson', 'Address': 'steve.watson#example.org'},
'Cc': [],
'Resent-Cc': [],
'Bcc': [ {'Name': 'Daryl Hurstbridge', 'Address': 'daryl.hurstbridge#example.org'},
{'Name': 'Sally Hervorth', 'Address': 'sally.hervorth#example.org'},
{'Name': 'Mike Merry', 'Address': 'mike.merry#example.org'},
{'Name': 'Jenny Callisto', 'Address': 'jenny.callisto#example.org'}
],
'To': {'Name': 'Darius Jedburgh', 'Address': 'darius.jedburgh#example.org'}
}
Use the iteritems() method on the dict. It's clear and easy to understand: that seems Pythonic to me. iteritems() also creates less temporary items than items(), as Preet Kukreti mentioned in the comments. First, fix your data. Right now, some of the values in the top-level dict are lists, and some are more dicts:
# list
'Delivered-To': [],
# dict
'From': {'Name': 'Steve Watson', 'Address': 'steve.watson#example.org'},
This means you have to check the type of the value and act accordingly (and you might forget to check!). Make your data consistent:
# list
'Delivered-To': [],
# also list
'From': [{'Name': 'Steve Watson', 'Address': 'steve.watson#example.org'}],
This will prevent weird type-related bugs in the future. Since Python is an interpreted language, it's very easy to make type bugs and not notice until your code is in production and crashes. Try to make your code as type-safe as possible!
Then you can use something like this:
for k, v in d.iteritems():
for row in v:
if "Name" in row and "Address" in row:
print row["Name"], ":", row["Address"]
One way is to change the lone dicts into a list containing the dict. Then all the entries can be treated the same
>>> D = {
... 'Resent-Bcc': [],
... 'Delivered-To': [],
... 'From': {'Name': 'Steve Watson', 'Address': 'steve.watson#example.org'},
... 'Cc': [],
... 'Resent-Cc': [],
... 'Bcc': [ {'Name': 'Daryl Hurstbridge', 'Address': 'daryl.hurstbridge#example.org'},
... {'Name': 'Sally Hervorth', 'Address': 'sally.hervorth#example.org'},
... {'Name': 'Mike Merry', 'Address': 'mike.merry#example.org'},
... {'Name': 'Jenny Callisto', 'Address': 'jenny.callisto#example.org'}
... ],
... 'To': {'Name': 'Darius Jedburgh', 'Address': 'darius.jedburgh#example.org'}
... }
>>> L = [v if type(v) is list else [v] for v in D.values()]
>>> [(d["Name"], d["Address"]) for item in L for d in item ]
[('Steve Watson', 'steve.watson#example.org'), ('Daryl Hurstbridge', 'daryl.hurstbridge#example.org'), ('Sally Hervorth', 'sally.hervorth#example.org'), ('Mike Merry', 'mike.merry#example.org'), ('Jenny Callisto', 'jenny.callisto#example.org'), ('Darius Jedburgh', 'darius.jedburgh#example.org')]
Or the one liner version
[(d["Name"], d["Address"]) for item in (v if type(v) is list else [v] for v in D.values())]
It's probably best to keep your data simple, by making the naked dict's be a list of one element holding the original dict. Otherwise, you're kind of asking for harder to test code.
I tend to lean away from isinstance(foo, dict) and instead use things like:
if getattr(d, 'iteritems'): print list(d.iteritems())
...It strikes me as more duck-typed this way; it opens the door to using one of the many dict-replacements - things that act like a dict, but nominally aren't a dict.
for key in header:
if header[key] and type(header[key])==type([]):
for item in header[key]:
print (item)
elif type(header[key])==type({}):
print(header[key])
# this option is not the easiest to read, so I classify it as less "pythonic"
l = [header[key] for key in header if header[key] and type(header[key])==type({})] + [header[key][i] for key in header if header[key] and type(header[key])==type([]) for i in range(len(header[key]))]
for item in l:
print(item)
if you're looking for the contents of a specific header you could modify the if statements accordingly. Both of these examples print the dictionaries, but could easily be adapted to print specific values.
for i in dict:
if 'Name' in dict[i]:
print (dict[i]['Name'],dict[i]['Address'])
this will not work for the bcc where its in a list (right now it will only print the from and to names and addresses) Do you need it to print the bcc addresses too?

Categories