One-line expression to map dictionary to another - python

I have dictionary like
d = {'user_id':1, 'user':'user1', 'group_id':3, 'group_name':'ordinary users'}
and "mapping" dictionary like:
m = {'user_id':'uid', 'group_id':'gid', 'group_name':'group'}
All i want to do is "replace" keys in first dictionary with values from the second one. The expected output is:
d = {'uid':1, 'user':'user1', 'gid':3, 'group':'ordinary users'}
I know that keys are immutable and i know how to do it with 'if/else' statement.
But maybe there is way to do it in one line expression?

Sure:
d = dict((m.get(k, k), v) for (k, v) in d.items())

Let's take the excellent code from #karlknechtel and see what it does:
>>> d = dict((m.get(k, k), v) for (k, v) in d.items())
{'gid': 3, 'group': 'ordinary users', 'uid': 1, 'user': 'user1'}
But how does it work?
To build a dictionary, you can use the dict() function. It expects a list of tuples. In 3.x and >2.7, you can also use dictionary comprehension (see answer by #nightcracker).
Let's dissect the argument of dict. At first, we need a list of all items in m. Every item is a tuple in the format (key, value).
>>> d.items()
[('group_id', 3), ('user_id', 1), ('user', 'user1'), ('group_name', 'ordinary users')]
Given a key value k, we could get the right key value from m by doing m[k].
>>> k = 'user_id'
>>> m[k]
'uid'
Unfortunately, not all keys in d also exist in m.
>>> k = 'user'
>>> m[k]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'user'
To work around that, you can use d.get(x, y), which returns d[x] if the key x exists, or the default value y if it doesn't. Now, if a key k from d doesn't exist in m, we just keep it, so the default is k.
>>> m.get(k, k).
'user'
Now we are ready to build a list of tuples to supply to dict(). To build a list in one line, we can use list comprehension.
To build a list of squares, you would write this:
>>> [x**2 for x in range(5)]
[0, 1, 4, 9, 16]
In our case, it looks like this:
>>> [(m.get(k, k), v) for (k, v) in d.items()]
[('gid', 3), ('uid', 1), ('user', 'user1'), ('group', 'ordinary users')]
That's a mouthful, let's look at that again.
Give me a list [...], which consists of tuples:
[(.., ..) ...]
I want one tuple for every item x in d:
[(.., ..) for x in d.items()]
We know that every item is a tuple with two components, so we can expand it to two variables k and v.
[(.., ..) for (k, v) in d.items()]
Every tuple should have the right key from m as first component, or k if k doesn't exist in m, and the value from d.
[(m.get(k, k), v) for (k, v) in d.items()]
We can pass it as argument to dict().
>>> dict([(m.get(k, k), v) for (k, v) in d.items()])
{'gid': 3, 'group': 'ordinary users', 'uid': 1, 'user': 'user1'}
Looks good! But wait, you might say, #karlknechtel didn't use square brackets.
Right, he didn't use a list comprehension, but a generator expression. Simply speaking, the difference is that a list comprehension builds the whole list in memory, while a generator expression calculates on item at a time. If a list on serves as an intermediate result, it's usually a good idea to use a generator expression. In this example, it doesn't really make a difference, but it's a good habit to get used to.
The equivalent generator expressions looks like this:
>>> ((m.get(k, k), v) for (k, v) in d.items())
<generator object <genexpr> at 0x1004b61e0>
If you pass a generator expression as argument to a function, you can usually omit the outer parentheses. Finally, we get:
>>> dict((m.get(k, k), v) for (k, v) in d.items())
{'gid': 3, 'group': 'ordinary users', 'uid': 1, 'user': 'user1'}
There happens quite a lot in one line of code. Some say this is unreadable, but once you are used to it, stretching this code over several lines seems unreadable. Just don't overdo it. List comprehension and generator expressions are very powerful, but with great power comes great responsibility. +1 for a good question!

In 3.x:
d = {m.get(key, key):value for key, value in d.items()}
It works by creating a new dictionary which contains every value from d and mapped to a new key. The key is retrieved like this: m[key] if m in key else key, but then with the default .get function (which supports default values if the key doesn't exist).

Why would you want to do it in one line?
result = {}
for k, v in d.iteritems():
result[m.get(k, k)] = v

Related

How to switch between keys and values in python dictionary in place (without changing it's location in memory)

i was asked to write a code including a function- reverse_dict_in_place(d)
which switches between keys and values of the inputed dictionary
without changing the dictionary's location in memory (in place).
however, testing it with id() function shows that all my solutions do change dictionaries memory location..
def reverse_dict_in_place(d):
d={y:x for x,y in d.items()}
return d
Alternative to current ones which allows values to be same as keys. Works in mostly the same way though, however once again no two values may be the same.
def reverse_dict_in_place(d):
copy = d.copy().items()
d.clear()
for k, v in copy:
d[v] = k
return d
>>> x = {0: 1, 1: 2}
>>> y = reverse_dict_in_place(x)
>>> id(x) == id(y)
True
>>>
Some assumptions for this to work (thanks to all the users who pointed these out):
There are no duplicate values
There are no non-hashable values
There are no values that are also keys
If you're comfortable with those assumption then I think this should work:
def reverse_dict_in_place(d):
for k,v in d.items():
del d[k]
d[v] = k
return d
Extending on Gad suggestion, you could use dict comprehension:
reversed = {v: k for k, v in d.items()}
Where d is a dict, and the same assumptions apply:
There are no duplicate values
There are no non-hashable values
There are no values that are also keys
This would not work, without modification, for nested dicts.
Note: #NightShade has posted a similar answer as my below answer, earlier than I posted.
You can try this:
def reverse_dict_in_place(d):
d_copy = d.copy()
d.clear()
for k in d_copy:
d[d_copy[k]] = k
This would work even if one of the dictionary's values happens to also be a key (as tested out below)
Testing it out:
my_dict = {1:1, 2:'two', 3:'three'}
reverse_dict_in_place(my_dict)
print (my_dict)
Output:
{1: 1, 'two': 2, 'three': 3}

Conditional list from a dictionary vs. Searching a dictionary with a condition - Python

I have a dictionary:
d = {'red':1,'green':2,'blue':3}
I am allowed to create a list of keys from this dictionary that match a condition using the following syntax:
mylist = [k for k in d if d[k] > 1]
print mylist
>>>> ['blue', 'green']
But if I try to search through a dictionary with a condition using a similar line of code, this does not work:
for k in d if d[k] > 1:
print k, d[k]
>>>>for k in d if d[k] > 1:
^
SyntaxError: invalid syntax
Can anyone explain to me why this is not the case? Or if I am doing something wrong? It seems like if I am allowed to combine looking through the dictionary with a conditional statement when making a list, I should also be allowed to do this when looking through the dictionary without making a list.
I am working in Python 2.7.8
Instead of:
for k in d if d[k] > 1:
print k, d[k]
You should simply be writing:
for k in d:
if d[k] > 1:
print k, d[k]
The list-comprehension syntax that puts it all on one [k for k in d if d[k] > 1] line is just syntactic sugar for the above.
Edit: Though as I mentioned in the comments below, you can do something tricky with generator comprehensions. A generator comprehension, in case anyone who reads this answer later doesn't know, is similar to a list comprehension, but it produces a generator, an iterable object that you can stick into a for loop (or stick in a variable for later processing). So you can do:
g = (k for k in d if d[k] > 1)
and later on,
for k in g:
print k, d[k]
Or, combined into one operation (though this is a little bit ugly):
for k in (x for x in d if d[x] > 1):
print k, d[k]
Note that the x inside the generator comprehension could have been k as well, but I chose to use a different name so it would be clearer what's going on. The name x is not in scope outside the comprehension, by the way:
for k in (x for x in d if d[x] > 1):
print k, d[k], x
produces:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: name 'x' is not defined
As I said, that's a bit ugly since you end up having to write the for statement twice. (Once for the "what the generator does" code and once for the "I want to unroll the generator and get its results" code). But it's the closest you'll get to the "I want to do this in one line" syntax you're looking for.
I agree with #rmunn. If you however insist on one line, then this will work:
d = {'red':1,'green':2,'blue':3}
for k, v in ((k, v) for (k, v) in d.items() if v > 1):
print k, v
It only does one dictionary lookup. If you go for the solution provided by #rmunn, then I would reduce the dictionary lookups to just one.
for k, v in d.items():
if v > 1:
print k, v
As pointed out by #rmunn in the comments it is even better to use the lazy iteritems() in Python 2, which returns an iterator:
for k, v in d.iteritems():
if v > 1:
print k, v
In the first case it is a list comprehension.
The second case is nothing.

invert key and value in dictionary in python3 (the value not unique)

I know how to simple invert key and value in dictionary when value is unique.
But how to invert when the value is NOT unique.
According requirement, If the value appear more than once, I need use set to make them together.
Ex. input d = {'a':1, 'b':2,'c':1,'d':2} output d = {1,{'a','c'},2,{'b','c'}}
I write pretty stupid code show below, but because I only create one set, thus all the value which show more than one times are in that set.
def change(d):
inverted_l = list(map(lambda t:(t[1],t[0]), d.items()))
store_key = [] #for store the key to check if value appear more than one
new_d = {}
x = set()
for i in range(len(inverted_l)):
store_key.append(inverted_l[i][0])
for i in range(len(store_key)):
if store_key.count(store_key[i])> 1:
x.add(inverted_l[i][1]) #I think the problem is I need create set
#each time, but I don't know how to do that
new_d[store_key[i]] = x
else:
new_d[store_key[i]] = inverted_l[i][1]
return new_d
print(sorted(change({'a':1, 'b':2, 'c':1,'d':2}).items()))
my wrong output is [(1, {'c', 'd', 'b', 'a'}), (2, {'c', 'd', 'b', 'a'})]But I need [(1, {'a', 'c'}), (2, {'b', 'd'})]
Added: I try your code, but error occur when I test print(sorted(invert_dict({'a':1, 'b':2, 'c':1}).items()))
I want my result is [(1, {'a', 'c'}), (2, 'b')]
I am new for python, thank you for your help and time!
def invert_dict(d):
result = {}
for k in d:
if d[k] not in result:
result[d[k]] = set()
result[d[k]].add(k)
return {k: d[k] if len(d[k])>1 else d[k].pop() for k in d}
Traceback (most recent call last):
File "U:\test.py", line 9, in <module>
print(sorted(invert_dict({'a':1, 'b':2, 'c':1}).items()))
File "U:\test.py", line 7, in invert_dict
return {k: d[k] if len(d[k])>1 else d[k].pop() for k in d}
File "U:\test.py", line 7, in <dictcomp>
return {k: d[k] if len(d[k])>1 else d[k].pop() for k in d}
TypeError: object of type 'int' has no len()
I'm pretty sure you mean the desired output is not the set
d = {1,{'a','c'},2,{'b','c'}}
but rather the dictionary
d = {1:{'a','c'}, 2:{'b','c'}}
Just double checking here:-).
Anyway, I'd do it:
import collections
def invert_dict(d):
result = collections.defaultdict(set)
for k in d:
result[d[k]].add(k)
return dict(result)
The return could be simplified to return result if the dict subclass defaultdict is OK -- it's only necessary to turn it into a dict if the spec is very rigorous about that.
I imagine the next step is likely to be an "oops, imports are not allowed" to forbid collections.defaultdict, so I'm anticipating that -- in that case, do instead (e.g)
def invert_dict(d):
result = {}
for k in d:
if d[k] not in result:
result[d[k]] = set()
result[d[k]].add(k)
return result
Added: apparently the latest version is crucial (OP of course "forgot" to add the "no imports" constraint in the first place -- why do they keep doing this to me?! would it cost them anything to reveal all the constraints up front from the start in their Qs?!?!?!!) but a tweak is needed -- singleton sets need to be turned into non-sets of their only element (a horrible, terrible, no-good spec, making the resulting dictionary nearly unusable, and making me strongly wish to have a few sharp words with the no-goodnicks that appear to believe that making disgustingly bad specs improves their teaching, but, that's another rant).
Anyway, best is to add a post-processing step:
def invert_dict(d):
result = {}
for k in d:
if d[k] not in result:
result[d[k]] = set()
result[d[k]].add(k)
return {k: d[k] if len(d[k])>1 else d[k].pop() for k in d}
Nothing to hard: just "unwinding" singleton sets down to their one item with a pop. (What next -- the belated revelation of yet another silly arbitrary constraint such as "no if/else expressions"?!-)
Added (leaving the buggy code above): need to use result not d in the return statement clearly! I.e the final line must be
return {k: result[k] if len(result[k])>1 else result[k].pop() for k in result}

How do I remove key values in a dictionary?

Python language.
I know how to remove keys in a dictionary, for example:
def remove_zeros(dict)
dict = {'B': 0, 'C': 7, 'A': 1, 'D': 0, 'E': 5}
del dict[5]
return dict
I want to know how to remove all values with zero from the dictionary and then sort the keys alphabetically. Using the example above, I'd want to get ['A', 'C', 'E'] as a result, eliminating key values B and D completely.
To sort do I just use dict.sort() ?
Is there a special function I must use?
sorted(k for (k, v) in D.iteritems() if v)
Sometimes when you code you have to take a step back and try to go for your intent, rather than trying to do one specific thing and miss the entire big picture. In python you have this feature called list/dictionary comprehension, from which you can use to filter the input to get the results you desire. So you want to filter out all values in your dictionary that are 0, it's simply this:
{k, v for k, v in d.items() if v != 0}
Now, dictionaries are hash tables, by default they are not sortable, however there is a class that can help you with this in collections. Using the OrderedDict class to facilitate the sorting, the code will end up like this:
OrderedDict(sorted(((k, v) for k, v in d.items() if v != 0)), key=lambda t: t[0])
Also, it's highly inadvisable to name your variables with the same name as a builtin type or method, such as dict.

Efficient way to find the largest key in a dictionary with non-zero value

I'm new Python and trying to implement code in a more Pythonic and efficient fashion.
Given a dictionary with numeric keys and values, what is the best way to find the largest key with a non-zero value?
Thanks
Something like this should be reasonably fast:
>>> x = {0: 5, 1: 7, 2: 0}
>>> max(k for k, v in x.iteritems() if v != 0)
1
(removing the != 0 will be slightly faster still, but obscures the meaning somewhat.)
To get the largest key, you can use the max function and inspect the keys like this:
max(x.iterkeys())
To filter out ones where the value is 0, you can use a generator expression:
(k for k, v in x.iteritems() if v != 0)
You can combine these to get what you are looking for (since max takes only one argument, the parentheses around the generator expression can be dropped):
max(k for k, v in x.iteritems() if v != 0)
Python's max function takes a key= parameter for a "measure" function.
data = {1: 25, 0: 75}
def keymeasure(key):
return data[key] and key
print max(data, key=keymeasure)
Using an inline lambda to the same effect and same binding of local variables:
print max(data, key=(lambda k: data[k] and k))
last alternative to bind in the local var into the anonymous key function
print max(data, key=(lambda k, mapping=data: mapping[k] and k))
If I were you and speed was a big concern, I'd probably create a new container class "DictMax" that'd keep track of it's largest non-zero value elements by having an internal stack of indexes, where the top element of the stack is always the key of the largest element in the dictionary. That way you'd get the largest element in constant time everytime.
list=[1, 1, 2, 3,3]
s={items:list.count(items)for items in list} ##occurrence of list
largest_value=max(k for k, v in s.items() if v != 0) #largest number occurrence
print(largest_value)

Categories