1

Introduction to the problem

I have a list of numbers and I want to, given certain value, get the closest value in the list that fulfil the condition. This is: the difference between closest value and value itself should be less than 0.5

What have I done

I'm new with lambda expressions so may be I don't understand the working process of this function. I have based (copy) my code on this answer: from list of integers, get number closest to a given value

def min_in_limits(value,list):
    new_val = min(list, key=lambda x: float(abs(float(x)-float(value)))<0.5)
    return new_val

So, when I give these inputs:

list = [9,11,13]
value = 8.9

Function min_in_limits returns 11, instead of 9

When evaluating step by step lambda returns:

True, False, False

Why is this failing?

Less elegant, but functional

def less_elegant(value,list):
    dictionary = {}
    for i in list:
        temp = float(abs(float(i)-float(value)))
        if temp < 0.5:
            dictionary.update({temp:i})
    keys = dictionary.keys()
    return(dictionary[min(keys)])

Thank you!

Carlos
  • 51
  • 4

4 Answers4

2

key in min is used to remap values when comparing them. Current value is passed to the function given as key, and its output is used when comparing objects, instead of original objects. If you add < 0.5 to that function, instead of the distance as a number, you will be returning only a boolean flag that tells you if your number is close.

What you could use instead is this:

def min_in_limits(value, list_):
    value = float(value)  # if it is e.g. string
    new_val = min(list_, key=lambda x: abs(float(x) - value))
    if abs(new_val - value) < 0.5:
        return new_val
    return None

After you find your minimum, check if it is within your threshold of 0.5 and return None if it is not, meaning no value in list_ is close enough. Instead of hardcoding 0.5, you could pass it as a function argument.

You should be passing value which is float and list_ which is a list of floats or ints, in which case you can remove value = float(value) and replace float(x) with just x.

Furthermore, please do not use keywords (e.g. list) as variable/parameter names.

Luka Mesaric
  • 657
  • 6
  • 9
  • Thank you for your answer and explanation, In my code I usually use Spanish words for variables (lista in this case), thus I avoid the use of keywords. Anyway thank you for your advice – Carlos May 27 '20 at 09:44
1

There is a one liner you could use. Assuming l=[9, 11, 13] and v=8.9 you could use the following:

min(map(lambda x: x if float(abs(float(x) - float(v))) < 0.5 else float('inf'), l))

Here we are mapping all of the values of l that do not satisfy our condition to infinity. Then we take the smallest value by using the min function.

geoph9
  • 357
  • 3
  • 18
  • Thank you, a nice solution for the problem. Now I have to check this post: https://stackoverflow.com/questions/34264710/what-is-the-point-of-floatinf-in-python to understand float('inf') :) – Carlos May 27 '20 at 09:47
  • Happy to help!. `float('inf')` is just a really big float number. There is no way the condition `float('inf') - any_number < 0.5` will be `True` since subtracting from infinity is infinity (again). – geoph9 May 27 '20 at 09:54
1

In addition to @Luka's answer and because you were using dict.update in a for loop, you can also use a dictionary comprehension to perform the same operation if necessary :

def min_in_limits(value, li):
    value = float(value)
    d = {abs(i - value): i for i in li if abs(i - value) < 0.5}
    # `d` might be an empty dict
    return d[min(d.keys())] if d else None
mgc
  • 5,223
  • 1
  • 24
  • 37
0

There are many ways to write the functions, but I can shortly explaination why your original codes fail, in case you are curious:

def min_in_limits(value,list):
    new_val = min(list, key=lambda x: float(abs(float(x)-float(value)))<0.5)
    return new_val

Your lambda function is fine, but notice that when you apply min(data=, key=), the key arguement inside allows you to customise in which logic you want your data to be sorted (read here). Because your lambda function returns a list with value[True, False, False], applying min on [True, False, False] returns the last item False in the array, which corresponds to value=11.

You can examine the bahaviour of min() yourself, simply:

a=min(True,False,False)
b=max(True,False,False)
print (a,b)

gives your:

False True
tianlinhe
  • 991
  • 1
  • 6
  • 15