3

My Python is a bit rusty and I'm really having trouble with this super simple algorithm.

I'm trying to write a function that takes two parameters (target, values) to find which number in the values is closest to the target number; if there happens to be a tie, then select the lower number.

Here's my progress, but I've only ever selected the lower number without actually comparing the difference of the values in the list in comparison to the relative distance of the target.

So, in essence, my function hasn't actually completed the challenge correctly at all because 47 is actually closer to 46 than 31 is; however, if the numbers contained in the list were hypothetically 45 and 47, then they would be equally distant from my target number and so 45 should be printed and not 47.

I'd prefer answers that use simple for/if/while loops so that I can really practice my skills.

Other more advanced answers are also welcome with a detailed explanation.

EDIT

A few really good answers from you guys, much appreciated; I'm testing them as we speak and will choose the one most fitting to my style and use the rest as references, even if you have the best one-liner answer.

target = 46
values = [1, 22, 31, 47, 87, 99]

def closest_to_target(target, values):

    lower = []

    for number in values:
        if number < target:
            lower.append(number)

    if lower:
        lowest = sorted(lower, reverse=True)[0]
        return lowest

    else:
        return "Error handling array"


print closest_to_target(target, values)
pythlang
  • 328
  • 5
  • 15
  • do you actually want the closest number (in this example, `47`), or are you looking for the number just before (here `31`)? Also, are you attached to loops? I think list comprehensions are a bit better for this sort of thing – RishiG Apr 27 '18 at 23:13
  • I actually just added more information beginning with "so, in essence.." answering your question; yes, in this scenario 47 should actually return and not 31 – pythlang Apr 27 '18 at 23:14
  • Check out https://stackoverflow.com/questions/12141150/from-list-of-integers-get-number-closest-to-a-given-value... lots of solutions to the closest in list problem there. – RishiG Apr 27 '18 at 23:15
  • Good catch, I actually did check that exact post out, but I can't get it to work for some reason and it also assumes a sorted array. Unfortunately, I'm having a hard time trying to wrap my brain around such a simple problem without using lamba – pythlang Apr 27 '18 at 23:18

5 Answers5

4

You are only selecting numbers that are lesser than the target, irrespective of the difference. For a basic code example as you requested, you want to consider a modification on the linear search, but just keep track of the lowest difference and the value that produces this lowest difference.

    def closest(target, values):
        smallest_difference = #a really large value to start
        closest_number = None
        for number in values:
            diff = abs(number - target)
            if diff == smallest_difference and number < closest_number:
               closest_number = number
            if diff < smallest_difference:
               closest_number = number
               smallest_difference = diff
        return closest_number

The advantage of this method is that you don't create additional lists.

rneeja
  • 96
  • 3
  • can I do `smallest_difference= [ ]` because keeping it without anything after `=` throws an error. I like this answer because it is really showcasing programming skills, not just functions like `lamba` which are obviously nice, but not the best when trying to practice – pythlang Apr 27 '18 at 23:49
  • thank you for your answer, @meeja ; your answer fit my criteria perfectly and within the style in which I wrote the original code so I have accepted your answer, although there are other answers that are also just as great and informative. – pythlang Apr 28 '18 at 21:08
3

You can use the following function which creates tuples of values, and how close the value is to the target value. By sorting by this tuple, you not only get the element closest to the target value, but in the case of ties, will pick the smallest of the tied values.

target = 46
values = [1, 22, 31, 47, 87, 99]

def closest_min(l, t):
  return sorted([(abs(t-i), l[e]) for e, i in enumerate(l)])[0][1]

print(closest_min(values, target))

Output:

47

In the case of a tie, will correctly choose the lowest:

>>> closest_min([47, 45], 46)
45

A slightly neater version using min():

def closest_min(l, t):
      return min(l, key=lambda x:(abs(x-t), x))
user3483203
  • 50,081
  • 9
  • 65
  • 94
3

This can be done in a single pass, keeping track of the deltas. If the delta is equal to the previous delta, the closest is the min of the previous closest and the current. If the delta is less, then update both the delta and the closest with the current values. Anything else, keep on going.

>>> target = 46
>>> values = [1, 22, 31, 47, 87, 99]
>>> closest = values[0]
>>> delta = abs(target - closest)
>>> for x in values:
...     d = abs(target - x)
...     if d == delta:
...         closest = min(closest, x)
...     elif d < delta:
...         delta = d
...         closest = x
...
>>> delta
1
>>> closest
47
>>>
juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
1
def min_dist_index(target, values):
    values = sorted(values)
    dists = [abs(target-value) for value in values]
    index = dists.index(min(dists))
    return values[index]

target = 46
values = [1, 22, 31, 47, 87, 99]
print(min_dist_index(target, values))

Output: 47

progmatico
  • 4,714
  • 1
  • 16
  • 27
-1

You could generate a list of valid numbers which then could be processed with a so called list comprehension:

values[[abs(target-i) for i in values].index(min([abs(target-i) for i in values]))]

Here the list literal [] is used to define a generic algorithm instead of specific values what the contents of the list should be. Each element in the list, i, are derived from the list literal containing your values and are checked against the target number. If they do not qualify they are not contained within the new generated list.

Implementation:

#!/usr/bin/env python

target = 46
values = [1, 22, 31, 47, 87, 99]


def closest_to_target(target, values):
    lower = values[[abs(46-i) for i in values].index(min([abs(46-i) for i in values]))]
    return lower

print(closest_to_target(target, values))

Which will print:

47

What
  • 304
  • 1
  • 12
  • can you show me an implementation within a function. if it works, I'll accept your answer. – pythlang Apr 27 '18 at 23:10
  • I didn't downvote you by the way; also, that isn't bad but ONLY the closest number to the target number should print. – pythlang Apr 27 '18 at 23:15
  • if you can provide an answer that only prints the number closest to the target and no others, I'll accept your answer as it is extremely straightforward – pythlang Apr 27 '18 at 23:20
  • actually, this does almost the same exact thing as mine does, just more elegantly lol. it doesn't actually compare the difference in values unfortunately. also, getting the last item in the index I would just do `[-1]` at the end of the `lower=` – pythlang Apr 27 '18 at 23:26
  • 1
    Yea I noticed that too. Its more about the way of creating the array from absolute values and then using the index that has the lowest value to get the value back. – What Apr 27 '18 at 23:28
  • 1
    @pythlang I updated my post. This version returns 47. – What Apr 27 '18 at 23:41
  • 1
    Sorry to say, but your code looks _weird_. Especially `lower = values[[abs(target-i) for i in values].index(min(values))]`this part - with `values[[....].index(..)]` - if you change the target to 2 and the list to [100, 22, 31, 47, 87, 99] it crashes with `ValueError: 22 is not in list`. The fact you get 47 does not mean its overall correct. – Patrick Artner Apr 27 '18 at 23:47