Constructing Key:Value pair from list comprehension in Python - 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)

Related

Failure converting complex code to a list comprehension

NB: None of the suggested answers work for me as they are for-loop driven (which I already have working) and don't explain what is wrong with the list comprehension version.
I am trying to transform
('name:80', 'desc:100')
into
{'name': 80, 'desc': 100}
A working for-loop:
new_wrap = {}
for item in wrap:
k, v = item.split(':')
new_wrap[k] = int(v)
wrap = new_wrap
a non-working list-comprehension:
wrap = dict([
(k, int(v))
for item in wrap
for (k, v) in item.split(':')
])
which gives
Traceback (most recent call last):
File ..., line ..., in ...
for (k, v) in item.split(':')
ValueError: too many values to unpack
What am I doing wrong?
You don't want to iterate over item.split(':'), you want to unpack it. To do that you can introduce a nested comprehension or generator expression. That gives you an opportunity to create the new variables k and v:
dict([
(k, int(v))
for k,v in (item.split(':') for item in wrap)
])
I would then get rid of the square brackets. It's more efficient to use a generator expression that generates entries on the fly rather than a list comprehension that builds a whole list in memory just to throw it away when the dict is created.
dict((k, int(v))
for k,v in (item.split(':') for item in wrap))
You simplify it even further by using a dict comprehension with curly braces:
{k: int(v) for k,v in (item.split(':') for item in wrap)}
If you would like to stick to a comprehension that's one-level deep and don't mind using Python 3.8+, you can use an assignment expression to hold the result of the split in a variable for indexing:
{(t := s.split(':'))[0]: int(t[1]) for s in wrap}
You should separate splitting from conversion to an int:
{k: int(v) for k, v in [w.split(":") for w in wrap]}
#{'name': 80, 'desc': 100}
But, as roganjosh correctly suggested, you gain nothing from this.
With the insight provided by John Kugelman I was able to figure out the problem and the solution I wanted.
The working code from the for loop:
k, v = item.split(':')
Breakdown:
item.split(':') == 'name:80'.split(':') == 'name', '80'
k, v = 'name', '80'
k == 'name' and v == 80
The non-working code in the list-comp:
for (k, v) in item.split(':')
Breakdown:
item.split(':') == 'name:80'.split(':') == 'name', '80'
for (k, v) in ('name', '80')
And there's the problem: in is not the same as =. in iterates over its arguments, returning one at a time, so:
k, v = 'n', 'a', 'm', 'e' is what is attempted, and because four items are trying to be stuffed into two we get the ValueError: too many values to unpack exception.
The simple solution is to encapsulate the item.split() into a tuple of its own:
for k, v in (item.split(':'), )
which becomes
for k, v in (('name', '80'), )
and since the first item in the tuple is a tuple we get
k, v = 'name', '80' for the first (and only) iteration of the second for loop
and the problem is solved without nesting another list-comp inside the primary list-comp.
The final, working list-comp:
[
(k, int(v))
for item in wrap
for (k, v) in (item.split(':'), )
]
i found this solution:
x = ('name:80', 'desc:100')
dict([tuple(a.split(':')) for a in x])

Python: How do you determine if a list of unordered tuples is IN a dictionaries.values()?

Im trying to iterate through one dictionary and determine if during iteration the value is present in a second dictionary. The values are lists with tuple coordinates at list items. When I run the following expected results don't return. What could I be doing wrong?
o={123:[(2045414.2025330812, 737011.67879535258), (2045345.5412850082, 736965.27060331404)]}
t={234:[(2053962.2499010414, 731325.2501180619), (2053955.6251330376, 731121.18739786744)]}
y={345:[(2045414.2025330812, 737011.67879535258), (2045345.5412850082, 736965.27060331404)]}
h={456:[(2045345.5412850082, 736965.27060331404), (2045414.2025330812, 737011.67879535258)]}
for k, v in o.items():
if v in h.values():
print k, v
If you mean that the list of tuples is itself unordered, then perhaps that is the problem in that you are comparing v to something in h.values(), but where h.values() has the list in a different order. Why not simply store sets as the values?
checker = set(list(map(set, h.values))
for k, v in o.items():
if set(v) in checker:
print(k,v)
Perhaps you can create your data structures in this format to avoid the unnecessary overhead.
You are comparing an ordered list of tuples. To compare an unordered collection of tuples, you can use set:
for k, v in o.items():
if set(v).issubset(set(next(iter(h.values())))):
print(k, v)
123 [(2045414.2025330812, 737011.6787953526), (2045345.5412850082, 736965.270603314)]
An alternative way of writing the above logic:
for k, v in o.items():
if set(v) <= set(list(h.values())[0]):
print(k, v)
The tricky part is converting dict.values() to a set. Since h only has one value, you need to extract the only value before conversion to set.

How to create a nested dictionary

so I have some problems with my dictionaries in python. For example I have dictionary like below:
d1 = {123456:xyz, 892019:kjl, 102930491:{[plm,kop]}
d2= {xyz:987, kjl: 0902, plm: 019240, kop:09829}
And I would like to have nested dictionary that looks something like that.
d={123456 :{xyz:987}, 892019:{kjl:0902}, 102930491:{plm:019240,kop:09829}}
is this possible? I was searching for nested dictionaries but nothing works for me.
You just need to loop through d1 and assign the correct values from d2.
Code:
d1 = {123456:'xyz', 892019:'kjl', 102930491:['plm','kop']}
d2 = {'xyz':987, 'kjl': 902, 'plm': 19240, 'kop':9829}
result = {}
for k, v in d1.items():
if isinstance(v, list):
result[k] = {k: d2.get(k) for k in v}
else:
result[k] = {v: d2.get(v)}
print(result)
Output:
{123456: {'xyz': 987}, 892019: {'kjl': 902}, 102930491: {'plm': 19240, 'kop': 9829}}
The above uses isintance() to check if the value is a list, so you can loop over it and assign the correct values from d2. It also uses dict.get() to ensure None is added if a KeyError was encountered when searching for a key in d2.
One-liner:
{k: {k: d2.get(k) for k in v} if isinstance(v, list) else {v: d2.get(v)} for k, v in d1.items()}
But this becomes messy and less maintainable.
First, you hate to put brackets around your keys and values.
d1 = {'123456':'xyz', '892019':'kjl', '102930491':'plm'}
d2= {'xyz':'987', 'kjl':'0902', 'plm':'019240', 'kop':'09829'}
Then I suggest you do a for loop
for key, value in d1.iter():
d1[key] = d2[value]
I have ignored your {[plm,kop]} thing. But it wont be difficult to implement it if you understand this first example.

Condensing the following code, preferably using a dictionary comprehension

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.

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.

Categories

Resources