yodish
yodish

Reputation: 805

Finding the closest value in a Python dictionary and returning its key

I have a value

value = 0.04532

Also, a dictionary similar to this

{'1': 0.02827, '2': 0.0, '3': 0.09827, '4': 0.04533}

I want to simply return the key of the value in the dictionary that is closest to my original value, 4 in this case

I found an older post that does something like this

newValue = value
answer = min(dict.items(), key=lambda (_, value): abs(value - newValue))

This returns the tuple ('4', 0.04533) and I just want the key (as an int). Furthermore, I'm having trouble understanding what that code is doing exactly.

Is there a cleaner way to get this done?

Upvotes: 12

Views: 10312

Answers (3)

jpp
jpp

Reputation: 164773

You can use sequence unpacking to unpack your tuple result:

value = 0.04532
d = {'1': 0.02827, '2': 0.0, '3': 0.09827, '4': 0.04533}

res_key, res_val = min(d.items(), key=lambda x: abs(value - x[1]))

print(res_key, res_val, sep=', ')

4, 0.04533

Half the problem appears to be in your choice of variables. Make sure you don't shadow class names, e.g. call your dictionary d, not dict. Likewise, don't name an argument of your lambda the same as a variable you've already defined.

Otherwise, the logic via min works as follows:

  1. Take each tuple extracted from d.items, i.e. key-value pairs.
  2. Apply the function lambda x: abs(value - x[1]) to each tuple, i.e. calculate the absolute difference versus value.
  3. Calculate the minimum result from the lambda function and return the argument supplied, in this case a single tuple from d.items.

Note PEP 3113 removes tuple parameter unpacking from Python 3.x, which is why we must explicitly extract the first value via x[1] in our lambda.

Upvotes: 10

timgeb
timgeb

Reputation: 78750

dict.items gives you the (key, value) pairs of the dictionary.

>>> d = {'1': 0.02827, '2': 0.0, '3': 0.09827, '4': 0.04533}
>>> d.items()
dict_items([('1', 0.02827), ('3', 0.09827), ('2', 0.0), ('4', 0.04533)])

The return value is iterable and therefore can be passed to min.

min also takes a keyword argument key which has to be a callable (usually a function). It is the criterion by which min evaluates the elements from its first argument in order to find the minimum.

Here is a much simpler example. Suppose we need to find the shortest list within a list of lists.

>>> lists = [[1, 2], [1, 2, 3], [5]]
>>> min(lists, key=len)
[5]

min iterates over lists, calls len(sublist) for every sublist in lists and returns the element where len(sublist) was minimal.

Examining, the proposed solution,

lambda (_, value): abs(value - newValue)

is an (anonymous) function (using Tuple Parameter Unpacking which no longer works in Python 3) supposed to take an item, a (key, value) pair, and return abs(value - newValue), which measures how close value is to newValue.

If the lambda confuses you, here's how you would write that key function in Python 3 as a normal function.

>>> def criterion(dict_item):
...     key, value = dict_item # unpack
...     return abs(value - newValue)

You can now issue

>>> newValue = 0.04532
>>> d = {'1': 0.02827, '2': 0.0, '3': 0.09827, '4': 0.04533}
>>> 
>>> min(d.items(), key=criterion)
('4', 0.04533)

If you want just the key as an integer, get it from index 0 and turn it into an int.

>>> int(min(d.items(), key=criterion)[0])
4

Finally, don't use the variable name dict, that name should be reserved for the builtin dictionary type.

Upvotes: 4

LeKhan9
LeKhan9

Reputation: 1350

That code you referenced is simply looping through the key value pairs in the dictionary, computing the differences with your expected value and returning the minimum key value pair.

To just obtain the value, you can simply do this:

answer = min(dict.items(), key=lambda (_, value): abs(value - newValue))[1]
  • note the indexing above as [1]

Are you sure you want it as an int type, it'll round down to the nearest integer

I would suggest just wrapping the statement above with a float()

Upvotes: 3

Related Questions