Condensing the following code, preferably using a dictionary comprehension - python

Is it possible (and, more importantly, practical) to write the following code as a dict comprehension?
I am creating a dictionary here and then checking for "blank" values (represented by a '-') and replacing it with another string value.
test_dict = dict(zip(list_one,list_two))
for k,v in test_dict.items():
if v == '-':
test_dict[k] = 'missing'

Why not do the replacement when you are creating the dictionary?
test_dict = dict(zip(list_one,
('missing' if x == '-' else x for x in list_two)))
Or, if you have an existing dictionary you can create a new one using:
{k: 'missing' if v == '-' else v for k, v in d.items()}
If you wish to alter the existing dictionary in-place then really there is nothing wrong with the existing code. You could use a list comprehension such as:
[d.__setitem__(k, 'missing') for k, v in d.items() if v == '-']
but that is rather ugly.

Related

Dictionary sized change due to iteration of dict

I am attempting to remove key-value pairs from a dict when a sub-dictionary matches values from another dictionary.
Example set-up:
e = {'a':{'aa':'yes'}, 'b':{'ac':'no'}, 'a':{'aa':'yes'}}
f = {'a':{'aa':'yes'}, 'e':{'ab':'no'}, 'a':{'aa':'yes'}}
for keys, values in e.items():
for k, v in f.items():
if values.get('aa') == v.get('aa'):
e.pop(keys)
RuntimeError: dictionary changed size during iteration
Expected result:
#from
e = {'a':{'aa':'yes'}, 'b':{'ac':'no'}, 'a':{'aa':'yes'}}
#to
e = {'b':{'ac':'no'}}
With single dict comprehension:
e = {k:v for k, v in e.items() if v.items() != f.get(k, {}).items()}
{'b': {'ac': 'no'}}
dict.get(key[, default]) allows you to set the needed(or preferred) default value returned for the key in dict
In general, you should not add or remove items from iterables that you are currently iterating over.
As you've been told, you can't modify the length of a thing while you're iterating it. There are a few options here, such as:
Saving a list of what you want to remove, then doing it later:
to_remove = []
for keys, values in e.items():
for k, v in f.items():
if values.get('aa') == v.get('aa'):
to_remove.append(keys)
for tr in to_remove:
e.pop(tr)
Cloning the object, so that what you're iterating does not change but the original object can be modified. This is even more memory expensive than the previous however.
for keys, values in dict(e).items(): # or list(e.items())
for k, v in f.items():
if values.get('aa') == v.get('aa'):
e.pop(keys)
You could also, in your case, simply create a new object:
g = {}
for keys, values in e.items():
for k, v in f.items():
if values.get('aa') != v.get('aa'):
g[keys] = values

Constructing Key:Value pair from list comprehension in Python

I am trying to extend some code.
What works:
pretrained_dict = {k: v for k, v pretrained_dict.items if k in model_dict}
However, if I extend it to:
pretrained_dict = {k: v if k in model_dict else k1:v1 for k, v, k1, v1 in zip(pretrained_dict.items(), model_dict.items()) }
The code fails, If I put the else at the end it still fails:
pretrained_dict = {k: v if k in model_dict for k, v, k1, v1 in zip(pretrained_dict.items(), model_dict.items()) else k1:v1}
^
SyntaxError: invalid syntax
How can I construct the key value pair using an if else condition over two lists?
You can use a ChainMap to achieve what you want without having to use comprehensions at all
from collections import ChainMap
pretrained_dict = ChainMap(pretrained_dict, model_dict)
This returns a dictionary-like object that will lookup keys in pretrained_dict first and if it is not present then lookup the key in model_dict
The reason that the second comprehension doesn't work is that the ternary operator only applies in the value. Luckily, both cases apply to the same key, so you can actually simplify the syntax a little. If that was not the case, you'd have to use two separate ternary operators or a for loop.
Another problem is that you don't show the grouping in your loop variables. dict.items yields tuples, and you have to make it clear how to unpack them.
So:
pretrained_dict = {k: v if k in model_dict else v1 for (k, v), (k1, v1) in zip(pretrained_dict.items(), model_dict.items())}
However, this won't actually do any of the lookup that you want. If your goal is to accept keys from pretrained_dict into model_dict in bulk, then you need to use model_dict.update with the appropriate keys. Zipping two dictionaries together is generally meaningless, since they won't have the same keys, and so it's unclear what the result would even be. Using a comprehension here isn't keeping with the literal requirement here either, since it necessarily means replacing rather than updating. In either case, your result should affect model_dict, not pretrained_dict.
Here is how you would do an update:
model_dict.update((k, v) for k, v in pretrained_dict.items() if k in model_dict)

Python 2.7 Improve algorithm that checks if a dictionary key is not in a list and if so deletes that key

I was playing around with a few solutions but am looking for an optimal and clean way to do this in Python. Basically I have a dictionary called 'd', and a list 'l'. If the dictionary contains a value not in the list delete that value in the dictionary. So given:
d = {1 : "bob", 2 : "mary", 3 : "joe"}
l = [2,3]
The first key of d gets deleted.
d = {2 : "mary", 3 : "joe"}
l = [2,3]
I have a working solution but feel the use of flags is unnecessary.
flag = False
del_vals = list()
for key in d.iterkeys():
for i in l:
if key == i: flag = True
if flag == False:
del_vals.append(key)
flag = False
for k in del_vals:
d.pop(k, None)
Is there a way I can write it to improve performance and make it more clean? Whether the use of generators or anything else is involved.
A list comprehension will work starting from Python 2.7:
d = {k: v for k, v in d.iteritems() if k in l}
Use items() instead of iteritems() in Python 3:
d = {k: v for k, v in d.items() if k in l}
You can also use sets for achieving the same:
z={i:d[i] for i in set(d.keys()).intersection(l)}
or use the below
p={i:d[i] for i in d.keys() if i in l}
or do this
q={i:d[i] for i in set(d.keys()) & set(l)}
Alternatively, you could do this as well:
for i in set(d.keys()) - set(l):
del(d[i])

Is it possible to use dict comprehension in a for loop without creating a new dict?

In Python 3.5, I am trying to iterate over a for loop using dict comprehension, yet it does not seem to work as it does with list. The following will be reported as a syntax error (not by PyCharm, only at runtime):
for k, v in (k, v for k, v in {"invalid": 1, "valid": 2}.items() if k == "valid"): # syntax error
print("Valid: " + (str(v)))
While the following works:
for e in (e for e in ["valid", "invalid"] if e == "valid"): # works
print(e)
I know creating a new dict would work (e.g. ), but I want to avoid the overhead as I am just doing operations on the elements.
for k, v in {k: v for k, v in my_dict.items() if k == "valid"}.items(): # works
print("Valid: " + (str(k)))
Of course, I could use a plain old if k == "valid": continue condition inside the loop, but I would like to understand why dict comprehension seems to be more limited than with list.
Python needs parentheses around the first k, v.
((k, v) for k, v in {"invalid": 1, "valid": 2}.items() if k == "valid")
Otherwise it looks like you're trying to make a tuple with two elements:
k
v for k, v in ...
The second isn't syntactically valid. (It would be if you put parentheses around it, making it a generator expression.)
You need to create a tuple with k, v i.e (k,v):
for k, v in ((k, v) for k, v in {"invalid": 1, "valid": 2}.items() if k == "valid"):
^^^
That is what is causing the syntax error. If you did the same thing with a list, set comprehension etc.. it would be exactly the same, you need to use a container if you want to add more that one element. The second example (e for e... works because you are returning a single element each time, the first two examples are also generator expressions only the latter is actually a dict comp.

How to make a dictionary comprehension with inline if?

I am trying to combine a dictionary comprehension and an inline if statement.
The comprehension loops over all items and as long as the item has not the key id
it creates a new key: job[old_key].
Code
job = {'id':1234, 'age':17, 'name':'dev'}
args = {'job[%s]' % k:v if k != 'id' else k:v for k, v in job}
Wished output
print args
{'id':1234, 'job[age]':17, 'job[name]':'dev'}
A SyntaxError was raised.
args = {'job[%s]' % k:v if k != 'key' else k:v for k, v in job}
^
SyntaxError: invalid syntax
However, when I try to run my script Python complains about k:v.
How can I combine a dictionary comprehension and an inline if statement?
Note: I know that I can easily achieve that task with a for loop, but I just want
to combine these two elements.
The key and value parts are separate expressions. Use the conditional expression in just the key part:
args = {'job[%s]' % k if k != 'id' else k: v for k, v in job.iteritems()}
The : is not part of either expression, only of the dictionary comprehension syntax. You also need to loop over both keys and values; in Python 2, use job.iteritems(), Python 3, job.items().
Demo:
>>> job = {'id':1234, 'age':17, 'name':'dev'}
>>> {'job[%s]' % k if k != 'id' else k: v for k, v in job.iteritems()}
{'id': 1234, 'job[age]': 17, 'job[name]': 'dev'}
Extending the other answer, you can make it more readable as follows
get_key = lambda k: ('job[%s]' if k != 'id' else '%s') % k
args = { get_key(key): val for key, val in job.iteritems()}

Categories

Resources