Python - tuple unpacking in dict comprehension - python

I'm trying to write a function that turns strings of the form 'A=5, b=7' into a dict {'A': 5, 'b': 7}. The following code snippets are what happen inside the main for loop - they turn a single part of the string into a single dict element.
This is fine:
s = 'A=5'
name, value = s.split('=')
d = {name: int(value)}
This is not:
s = 'A=5'
d = {name: int(value) for name, value in s.split('=')}
ValueError: need more than 1 value to unpack
Why can't I unpack the tuple when it's in a dict comprehension? If I get this working then I can easily make the whole function into a single compact dict comprehension.

In your code, s.split('=') will return the list: ['A', '5']. When iterating over that list, a single string gets returned each time (the first time it is 'A', the second time it is '5') so you can't unpack that single string into 2 variables.
You could try: for name,value in [s.split('=')]
More likely, you have an iterable of strings that you want to split -- then your dict comprehension becomes simple (2 lines):
splitstrs = (s.split('=') for s in list_of_strings)
d = {name: int(value) for name,value in splitstrs }
Of course, if you're obsessed with 1-liners, you can combine it, but I wouldn't.

Sure you could do this:
>>> s = 'A=5, b=7'
>>> {k: int(v) for k, v in (item.split('=') for item in s.split(','))}
{'A': 5, ' b': 7}
But in this case I would just use this more imperative code:
>>> d = {}
>>> for item in s.split(','):
k, v = item.split('=')
d[k] = int(v)
>>> d
{'A': 5, ' b': 7}

Some people tend to believe you'll go to hell for using eval, but...
s = 'A=5, b=7'
eval('dict(%s)' % s)
Or better, to be safe (thanks to mgilson for pointing it out):
s = 'A=5, b=7'
eval('dict(%s)' % s, {'__builtins__': None, 'dict': dict})

See mgilson answer to why the error is happening. To achieve what you want, you could use:
d = {name: int(value) for name,value in (x.split('=',1) for x in s.split(','))}
To account for spaces, use .strip() as needed (ex.: x.strip().split('=',1)).

How about this code:
a="A=5, b=9"
b=dict((x, int(y)) for x, y in re.findall("([a-zA-Z]+)=(\d+)", a))
print b
Output:
{'A': 5, 'b': 9}
This version will work with other forms of input as well, for example
a="A=5 b=9 blabla: yyy=100"
will give you
{'A': 5, 'b': 9, 'yyy': 100}

>>> strs='A=5, b=7'
>>> {x.split('=')[0].strip():int(x.split('=')[1]) for x in strs.split(",")}
{'A': 5, 'b': 7}
for readability you should use normal for-in loop instead of comprehensions.
strs='A=5, b=7'
dic={}
for x in strs.split(','):
name,val=x.split('=')
dic[name.strip()]=int(val)

How about this?
>>> s
'a=5, b=3, c=4'
>>> {z.split('=')[0].strip(): int(z.split('=')[1]) for z in s.split(',')}
{'a': 5, 'c': 4, 'b': 3}

Since Python 3.8, you can use walrus operator (:=) for this kind of operation. It allows to assign variables in the middle of expressions (in this case, assign the list created by .split('=') to kv).
s = 'A=5, b=7'
{(kv := item.split('='))[0]: int(kv[1]) for item in s.split(', ')}
# {'A': 5, 'b': 7}
One feature is that it leaks the assigned variable, kv, outside the scope it was defined in. If you want to avoid that, you can use a nested for-loop where the inner loop is over a singleton list (as suggested in mgilson's answer).
{k: int(v) for item in s.split(', ') for k,v in [item.split('=')]}
Since Python 3.9, loops over singleton lists are optimized to be as fast as simple assignments, i.e. y in [expr] is as fast as y = expr.

Related

Is there any straightforward option of unpacking a dictionary?

If I do something like this
some_obj = {"a": 1, "b": 2, "c": 3}
first, *rest = some_obj
I'll get a list, but I want it in 2 dictionaries: first = {"a": 1} and rest = {"b": 2, "c": 3}. As I understand, I can make a function, but I wonder if I can make it in one line, like in javascript with spread operator.
I don't know if there is a reliable way to achieve this in one line, But here is one method.
First unpack the keys and values(.items()). Using some_obj only iterate through the keys.
>>> some_obj = {"a":1, "b":2, "c": 3}
>>> first, *rest = some_obj.items()
But this will return a tuple,
>>> first
('a', 1)
>>> rest
[('b', 2), ('c', 3)]
But you can again convert back to dict with just a dict call.
>>> dict([first])
{'a': 1}
>>> dict(rest)
{'b': 2, 'c': 3}
A oneliner inspired by Abdul Niyas P M's:
first, rest = dict([next(i := iter(some_obj.items()))]), dict(i)
Uses an assignment expression, introduced in Python 3.8 almost two years ago.
k = next(iter(some_obj)) # get the first key
first = {k: some_obj.pop(k)}
rest = some_obj
If you need to keep the original object intact - note this degrades from O(1) to O(n) in both time and space:
k = next(iter(some_obj))
rest = some_obj.copy()
first = {k: rest.pop(k)}
#AbdulNiyasPM's answer is perfectly fine, but since you asked for a one-liner, here's one way to do it (though you would have to do from operator import itemgetter first):
first, rest = map(dict, itemgetter(slice(0, 1), slice(1, None))(list(some_obj.items())))
If you prefer not to import anything, you can use a similar one-liner with a lambda function that takes one fixed argument and the rest as variable-length arguments:
first, rest = map(dict, (lambda f, *r: ((f,), r))(*some_obj.items()))
Demo: https://replit.com/#blhsing/ImpeccableEllipticalGeeklog

How to reduce the sets in dict values using comprehension?

I have
x = {'a':set([1]) , 'b':set([2]), 'c':set([3]) }
It is guaranteed that there is only one element in the set. I need to convert this to
{'a': 1, 'c': 3, 'b': 2}
Following works:
x1 = {k:x[k].pop() for k in x.keys()} OR
x1 = {k:next(iter(x[k])) for k in x.keys()}
but I am not liking it as pop() here is modifying the original collection. I need help on following.
How can I use unpacking as mentioned here within comprehension.
Is there any way, I can use functools.reduce for this.
What can be a better or Pythonic way of doing this overall?
If you want to do this with an unpacking, that'd be
{k: item for k, [item] in x.iteritems()}
In my opinion, the most readable option would be to use next and iter. Unpacking might also not be of much use since it is more of an assignment operation. (See user2357112's answer)
How about simply:
>>> {k: next(iter(v)) for k, v in x.items()}
{'a': 1, 'c': 3, 'b': 2}

Dictionary comprehension for swapping keys/values in a dict with multiple equal values

def invert_dict(d):
inv = dict()
for key in d:
val = d[key]
if val not in inv:
inv[val] = [key]
else:
inv[val].append(key)
return inv
This is an example from Think Python book, a function for inverting(swapping) keys and values in a dictionary. New values (former keys) are stored as lists, so if there was multiple dictionary values (bound to a different keys) that were equal before inverting, then this function simply appends them to the list of former keys.
Example:
somedict = {'one': 1, 'two': 2, 'doubletwo': 2, 'three': 3}
invert_dict(somedict) ---> {1: ['one'], 2: ['doubletwo', 'two'], 3: ['three']}
My question is, can the same be done with dictionary comprehensions? This function creates an empty dict inv = dict(), which is then checked later in the function with if/else for the presence of values. Dict comprehension, in this case, should check itself. Is that possible, and how the syntax should look like?
General dict comprehension syntax for swapping values is:
{value:key for key, value in somedict.items()}
but if I want to add an 'if' clausule, what it should look like? if value not in (what)?
Thanks.
I don't think it's possible with simple dict comprehension without using other functions.
Following code uses itertools.groupby to group keys that have same values.
>>> import itertools
>>> {k: [x[1] for x in grp]
for k, grp in itertools.groupby(
sorted((v,k) for k, v in somedict.iteritems()),
key=lambda x: x[0])
}
{1: ['one'], 2: ['doubletwo', 'two'], 3: ['three']}
You can use a set comprehension side effect:
somedict = {'one': 1, 'two': 2, 'doubletwo': 2, 'three': 3}
invert_dict={}
{invert_dict.setdefault(v, []).append(k) for k, v in somedict.items()}
print invert_dict
# {1: ['one'], 2: ['doubletwo', 'two'], 3: ['three']}
Here is a good answer:
fts = {1:1,2:1,3:2,4:1}
new_dict = {dest: [k for k, v in fts.items() if v == dest] for dest in set(fts.values())}
Reference: Head First Python ,2nd Edition, Page(502)

In python is there something like updated that is to update what sorted is to sort?

In python if I do the following:
>>> list = [ 3, 2, 1]
>>> sorted_list = k.sort()
Then sorted_list is None and list is sorted:
>>> sorted_list = k.sort()
>>> print list, sorted_list
[1, 2, 3] None
However, if I do the following:
>>> list = [ 3, 2, 1]
>>> sorted_list = sorted(list)
Then list remains unsorted and sorted_list contains a copy of the sorted list:
>>> print list, sorted_list
[3, 2, 1] [1, 2, 3]
I am wondering if there is an equivalent for the update function for dictionaries.
That way I could do something like this:
def foo(a, b, extra={}):
bar = { 'first': a, 'second': b }
special_function(**updated(bar, extra))
normal_function(**bar)
rather than having to do something like this:
def foo(a, b, extra={}):
bar = { 'first': a, 'second': b }
special_bar = bar.copy()
special_bar.update(extra) # [1]
special_function(**special_bar)
normal_function(**bar)
[1] Yes I realize I could simply replace these two lines with extra.update(bar) but let's assume I want to retain extra as is for later on in the function.
I realize I could implement this myself thusly:
def updated(old_dict, extra={}):
new_dict = old_dict.copy()
new_dict.update(extra)
return new_dict
Or the following highly unreadable in-place statement:
special_function(**(dict(bar.items()+extra.items())))
But I was hoping there was something built in that I could already use.
You can simply use the built-in dict():
updated_dict = dict(old_dict, **extra_dict)
If you need non-string keys, you can use a function like that: (It is not as ugly as your "in-place" expression + it works for any number of dictionaries)
from itertools import chain # ← credits go to Niklas B.
def updated(*dicts):
return dict(chain(*map(dict.items, dicts)))
updated({42: 'the answer'}, {1337: 'elite'}) # {42: 'the answer', 1337: 'elite'}
Otherwise Sven’s suggestion is just fine.
Edit: If you are using Python 2.7 or later, you can also use a dictionary comprehension, as Sven suggested in the comments:
def updated(*dicts):
return {k: v for d in dicts for k, v in d.items()}
I don't really see what's wrong in using two lines, like you do:
new_bar = bar.copy()
new_bar.update(extra)
It's clean and readable.
>>> d = {1:2, 3:4}
>>> e = {3:9, 5:25}
>>> f = d.copy()
>>> f.update(e)
>>> d
{1: 2, 3: 4}
>>> f
{1: 2, 3: 9, 5: 25}
>>> e
{3: 9, 5: 25}
In three words: Zen of Python.
To be more clear: My point is that I wouldn't replace those two lines with an updated() function that's not coming from the standard library.
If I was to stumble in a line of code like:
new_bar = updated(bar, extra)
I'd have to track that function down to see what it does. I couldn't trust that it doesn't something strange.
The OP also compared that with sorted(), but sorted() has it's reason to exist, it acts on everything that's iterable and does that with the amazing timsort. Instead what should be the behaviour of an hypothetical updated()? Should that maybe be a dict class method? It's really not clear IMHO.
Said so one could choose the OP two lines, or Sven's solution, or a dict comprehension/generator-expression, I think it's really just a matter of taste.

How do I exchange keys with values in a dictionary? [duplicate]

This question already has answers here:
Reverse / invert a dictionary mapping
(32 answers)
Closed 10 months ago.
I receive a dictionary as input, and would like to to return a dictionary whose keys will be the input's values and whose value will be the corresponding input keys. Values are unique.
For example, say my input is:
a = dict()
a['one']=1
a['two']=2
I would like my output to be:
{1: 'one', 2: 'two'}
To clarify I would like my result to be the equivalent of the following:
res = dict()
res[1] = 'one'
res[2] = 'two'
Any neat Pythonic way to achieve this?
Python 2:
res = dict((v,k) for k,v in a.iteritems())
Python 3 (thanks to #erik):
res = dict((v,k) for k,v in a.items())
new_dict = dict(zip(my_dict.values(), my_dict.keys()))
From Python 2.7 on, including 3.0+, there's an arguably shorter, more readable version:
>>> my_dict = {'x':1, 'y':2, 'z':3}
>>> {v: k for k, v in my_dict.items()}
{1: 'x', 2: 'y', 3: 'z'}
You can make use of dict comprehensions:
Python 3
res = {v: k for k, v in a.items()}
Python 2
res = {v: k for k, v in a.iteritems()}
Edited: For Python 3, use a.items() instead of a.iteritems(). Discussions about the differences between them can be found in iteritems in Python on SO.
In [1]: my_dict = {'x':1, 'y':2, 'z':3}
Python 3
In [2]: dict((value, key) for key, value in my_dict.items())
Out[2]: {1: 'x', 2: 'y', 3: 'z'}
Python 2
In [2]: dict((value, key) for key, value in my_dict.iteritems())
Out[2]: {1: 'x', 2: 'y', 3: 'z'}
The current leading answer assumes values are unique which is not always the case. What if values are not unique? You will loose information!
For example:
d = {'a':3, 'b': 2, 'c': 2}
{v:k for k,v in d.iteritems()}
returns {2: 'b', 3: 'a'}.
The information about 'c' was completely ignored.
Ideally it should had be something like {2: ['b','c'], 3: ['a']}. This is what the bottom implementation does.
Python 2.x
def reverse_non_unique_mapping(d):
dinv = {}
for k, v in d.iteritems():
if v in dinv:
dinv[v].append(k)
else:
dinv[v] = [k]
return dinv
Python 3.x
def reverse_non_unique_mapping(d):
dinv = {}
for k, v in d.items():
if v in dinv:
dinv[v].append(k)
else:
dinv[v] = [k]
return dinv
You could try:
Python 3
d={'one':1,'two':2}
d2=dict((value,key) for key,value in d.items())
d2
{'two': 2, 'one': 1}
Python 2
d={'one':1,'two':2}
d2=dict((value,key) for key,value in d.iteritems())
d2
{'two': 2, 'one': 1}
Beware that you cannot 'reverse' a dictionary if
More than one key shares the same value. For example {'one':1,'two':1}. The new dictionary can only have one item with key 1.
One or more of the values is unhashable. For example {'one':[1]}. [1] is a valid value but not a valid key.
See this thread on the python mailing list for a discussion on the subject.
res = dict(zip(a.values(), a.keys()))
new_dict = dict( (my_dict[k], k) for k in my_dict)
or even better, but only works in Python 3:
new_dict = { my_dict[k]: k for k in my_dict}
Another way to expand on Ilya Prokin's response is to actually use the reversed function.
dict(map(reversed, my_dict.items()))
In essence, your dictionary is iterated through (using .items()) where each item is a key/value pair, and those items are swapped with the reversed function. When this is passed to the dict constructor, it turns them into value/key pairs which is what you want.
Suggestion for an improvement for Javier answer :
dict(zip(d.values(),d))
Instead of d.keys() you can write just d, because if you go through dictionary with an iterator, it will return the keys of the relevant dictionary.
Ex. for this behavior :
d = {'a':1,'b':2}
for k in d:
k
'a'
'b'
Can be done easily with dictionary comprehension:
{d[i]:i for i in d}
dict(map(lambda x: x[::-1], YourDict.items()))
.items() returns a list of tuples of (key, value). map() goes through elements of the list and applies lambda x:[::-1] to each its element (tuple) to reverse it, so each tuple becomes (value, key) in the new list spitted out of map. Finally, dict() makes a dict from the new list.
Hanan's answer is the correct one as it covers more general case (the other answers are kind of misleading for someone unaware of the duplicate situation). An improvement to Hanan's answer is using setdefault:
mydict = {1:a, 2:a, 3:b}
result = {}
for i in mydict:
result.setdefault(mydict[i],[]).append(i)
print(result)
>>> result = {a:[1,2], b:[3]}
Using loop:-
newdict = {} #Will contain reversed key:value pairs.
for key, value in zip(my_dict.keys(), my_dict.values()):
# Operations on key/value can also be performed.
newdict[value] = key
If you're using Python3, it's slightly different:
res = dict((v,k) for k,v in a.items())
Adding an in-place solution:
>>> d = {1: 'one', 2: 'two', 3: 'three', 4: 'four'}
>>> for k in list(d.keys()):
... d[d.pop(k)] = k
...
>>> d
{'two': 2, 'one': 1, 'four': 4, 'three': 3}
In Python3, it is critical that you use list(d.keys()) because dict.keys returns a view of the keys. If you are using Python2, d.keys() is enough.
I find this version the most comprehensive one:
a = {1: 'one', 2: 'two'}
swapped_a = {value : key for key, value in a.items()}
print(swapped_a)
output :
{'one': 1, 'two': 2}
An alternative that is not quite as readable (in my opinion) as some of the other answers:
new_dict = dict(zip(*list(zip(*old_dict.items()))[::-1]))
where list(zip(*old_dict.items()))[::-1] gives a list of 2 tuples, old_dict's values and keys, respectively.

Categories