Prashanth Raghavan
Prashanth Raghavan

Reputation: 389

Using max together with the 'get' method for a dictionary

I was watching a presentation and it introduced me to this way of finding the maximum value in a dictionary:

c = {"One":1, "Two":2, "Uns":1}
max(c,key=c.get)
'Two'

I don't understand quite how it works. The 'get' method returns the value for a key, if the key exists in the dictionary. So does the 'max' iterate through each key, find the associated value and then return the key associated with the maximum value? How does it work when the dictionary has multiple maximum values?

c = {"One":1, "Two":2, "Uns":1, "Dos": 2}
max(c,key=c.get)
'Dos'

To experiment further, I tried this:

c = {"One":1, "Two":2, "Uns":1}
max(c.values(),key=c.get)
1

Why did it return '1' instead of '2'?

Upvotes: 1

Views: 751

Answers (2)

millimoose
millimoose

Reputation: 39960

When you call:

max(c, key=c.get)

the max function internally iterates over the first parameter; for a dictionary this will iterate over the keys of the dictionary, in... some order, this is where things get hairy.

  • before Python 3.6, this order was arbitrary and not guaranteed to be any specific order
  • in the CPython implementation of Python 3.6, the order was changed - as an implementation detail - to be the order in which the keys were inserted into the dictionary. (In your case, the order in which they're written in the literal, so "One", "Two", "Uns", "Dos"
  • as of Python 3.7, this behaviour is guaranteed by the language specification.

(See also the Python documentation on the dict type.)

Now we know that the max() function will see two possible keys that map to the maximum value, "Two" and "Dos", in this order. The documentation for the max() function states:

If multiple items are maximal, the function returns the first one encountered.

So in Python 3.7 as well as CPython 3.6, what is returned depends on the order in which the keys are inserted into the dictionary:

>>> c = {"Uns":1, "Dos":2, "One":1, "Two": 2}
>>> list(iter(c))
['Uns', 'Dos', 'One', 'Two']
>>> max(c, key=c.get)
'Dos'
>>> c = {"One":1, "Two":2, "Uns":1, "Dos": 2}
>>> list(iter(c))
['One', 'Two', 'Uns', 'Dos']
>>> max(c, key=c.get)
'Two'

In Python 3.5, which maximum will be the first encountered is arbitrary, but it will be the first key with a maximum value seen in list(iter(c))

In Python 2.7, the behaviour of the max() function I cited above isn't specified in the documentation, so who knows really.


As for your second example, it really plain doesn't make sense, so I'll just rush past it: c.get() will return None when you pass it a key that doesn't exist in the dictionary. In Python 2.x, it was allowed to compare None with None using the < operator that max() uses to determine the maximum; I'm guessing since it compares two same objects, they were considered equal and thus the result of that comparison was False, and every item in your case would be considered the "maximum". Since the order of the keys is unspecified in Python 2.x, and neither is which one is returned by max(), the result you get is essentially arbitrary. Not that it matters, since there is no sense the whole thing could be making.

You could prevent this by avoiding the use of dict.get(), and writing more assertive code that fails early because it's clear accessing a key that doesn't exist in the dictionary is a mistake:

max(c, key=lambda k: c[k])

Upvotes: 2

John La Rooy
John La Rooy

Reputation: 304255

Don't confuse the idea of the key of the dictionary, with the "key-function"

When two values are the same, max or min will return the first matching key. Since iteration of dict is unordered until Python3.6, it's not a good idea to rely on which particular key would be returned

In your last example, you are asking the dict to use the values as keys to the dict. This will usually return None unless the value also happens to be a key (This will never happen if all the keys are str and all the values are int, for example). In Python2, since None isn't greater than None, the first key that is encountered will be the one that is eventually returned. In Python3, doing this comparison throws an exception.

Upvotes: 1

Related Questions