1

I have a problem. In my code I need to do the following: I have an array like this: [1, 5, 7, 9, 10, 11, 14, 15]

Now I need to determine in the most efficient way what the closest values are for a given x. For example:

array = [1, 5, 7, 9, 10, 11, 14, 15]
above, below = myFunction(array, 8)

Should return 7 and 9

If there is no number higher or lower than the given number, the value that couldn't be determined should be None. If the same number is given as in the array, for example: 7. The values 5 and 9 should be returned.

I know there is a min() and max() function, but I couldn't find anything in my case. The only thing close to what I want, was manual looping through the array, but I was wondering if there was a more efficient way, because this code will be executed arround 500.000 times, so speed is important!

Is there a efficient way to determine those values?

A. Vreeswijk
  • 822
  • 1
  • 19
  • 57
  • 3
    Is the array all unique numbers? What is the return value if `array = [1, 5, 7, 7, 9, 9, 10]` Are the numbers always sorted? If sorted a binary search would find the point in the list where the query number would go – Mark Tolonen Apr 14 '22 at 00:02
  • All the numbers are unique. Not sorted, but I could do that – A. Vreeswijk Apr 14 '22 at 00:04
  • 1
    Quite possibly the code shouldn't be executed 500.000 times but you should achieve the same result in a different way... – Kelly Bundy Apr 14 '22 at 00:04
  • 1
    Sounds like a [XY Problem](https://mywiki.wooledge.org/XyProblem). What is the bigger problem you are trying to solve? Why would you do it half a million times? – Rodrigo Rodrigues Apr 14 '22 at 00:05
  • What should `myFunction([7,8,9],8)` return? – Kelly Bundy Apr 14 '22 at 00:07
  • @KellyBundy, that should return `7, 9` – A. Vreeswijk Apr 14 '22 at 00:08
  • And `myFunction([7,8,9],2)` or `myFunction([7,8,9],13)`? – Kelly Bundy Apr 14 '22 at 00:11
  • If there is nothing below or above, it should return a value `None` for that property. Sorry, for forgetting these details :( – A. Vreeswijk Apr 14 '22 at 00:13
  • [Edit](https://stackoverflow.com/posts/71865005/edit) the question with the details to make it a complete question. – Mark Tolonen Apr 14 '22 at 00:21
  • Sounds like you should sort the list and use [bisect.bisect_left()](https://docs.python.org/3/library/bisect.html?highlight=search#bisect.bisect_left) to locate the spot in the array where the number would go. Then decide what to report. – Mark Tolonen Apr 14 '22 at 00:26
  • `Not sorted, but I could do that` → Is initial array always same? Do you need to sort it anytime when running 500.000 iterations or it is possible to sort it one time before start iteration? – rzlvmp Apr 14 '22 at 00:40
  • Well, the array can get expanded in one of those 500.000 runs, so I think that I need to sort it everytime I add a new value, or is there a way to add it on the right spot? – A. Vreeswijk Apr 14 '22 at 00:43
  • You described only the part of task. Complexity of `sort` is `O(n log n)`. If you need to sort often you will spent many time for sorting. It will be faster to check all elements in unsorted array instead of sort it every time. If you need to sort array only couple of times it will be faster to work with sorted arrays. Also if you task is a really production problem and not synthetic codility interview question multiprocessing may help you more than effective logic – rzlvmp Apr 14 '22 at 00:52
  • `I need to sort it everytime I add a new value` → Here is important how often you plan to add new value. `or is there a way to add it on the right spot?` → Yes. The function you want to invent allow you to get index of the right spot. Because right spot is exactly between `above and below given x`. So correct logic is: 1. sort array, 2. iterate over given `x`s, 3. when you need to add `y` into array use same function for `y` to determine right spot, 4. insert `y` into right spot and keep elements sorted, 5. continue to iterate with `x`. – rzlvmp Apr 14 '22 at 01:05
  • 2
    You can use `bisect` to find the insertion point in a sorted list, so insert new items into an already sorted list instead of re-sorting the list – Mark Tolonen Apr 14 '22 at 01:05

3 Answers3

0
import bisect

def find_nearest(arr,value):
    arr.sort()
    idx = bisect.bisect_left(arr, value)

    if idx == 0:
        return None, arr[idx]
    elif idx == len(arr):
        return arr[idx - 1], None
    elif arr[idx] == value:
        return arr[idx], arr[idx]
    else:
        return arr[idx - 1], arr[idx]

array = [1, 5, 7, 9, 10, 11, 14, 15]

print(find_nearest(array, 0))
print(find_nearest(array, 4))
print(find_nearest(array, 8))
print(find_nearest(array, 10))
print(find_nearest(array, 20))

Output:

(None, 1)
(1, 5)
(7, 9)
(10, 10)
(15, None)

Helper source

BeRT2me
  • 12,699
  • 2
  • 13
  • 31
  • Only the result of `10` is incorrect. When entering `10`, I want the values `9, 11` – A. Vreeswijk Apr 14 '22 at 08:52
  • Is every value in the array guaranteed to be unique? If not, what would you want something like `array = [1, 5, 7, 9, 10, 10, 10, 11, 14, 15]` to return for 10? – BeRT2me Apr 14 '22 at 16:45
0

Assuming that your array is not sorted, you will be indeed forced to scan the entire array. But if it is sorted you can use a binary search approach that will run in O(log(n)), something like this:

def search_nearest_elems(array, elem):
    """
    Uses dichotomic search approach.
    Runs in O(log(n)), with n = len(array)
    """
    i = len(array) // 2
    left, right = 0, len(array) - 1
    while right - left > 1:
        if array[i] == elem:
            return elem, elem
        elif array[i] > elem:
            right, i = i, (left + i) // 2
        else:
            left, i = i, (right + i) // 2

    return array[left], array[right]

array = [1, 5, 7, 9, 10, 11, 14, 15]
assert search_nearest_elems(array, 8) == (7, 9)
assert search_nearest_elems(array, 9) == (9, 9)
assert search_nearest_elems(array, 14.5) == (14, 15)
assert search_nearest_elems(array, 2) == (1, 5)
bonnal-enzo
  • 1,165
  • 9
  • 19
0

This answer based on @Mark Tolonen hint about bisect

Let's say that you need to append one number on every iteration but initial array is the same.
All elements in array should be unique. Initial array may be not sorted.

import bisect as b


init_array = [5, 9, 10, 11, 14, 15, 1, 7]

init_array.sort()

numbers_to_append = [22, 4, 12, 88, 99, 109, 999, 1000, 1001]

numbers_to_check = [12, 55, 23, 55, 0, 55, 10, 999]

for (_, n) in enumerate(numbers_to_check):

  # get index of closer right to below
  i = b.bisect_left(init_array, n)

  # get above
  if i >= (len(init_array)):
    above = None
  else:
    if init_array[i] == n:
      try:
        above = init_array[i + 1]
      except IndexError:
        above = None
    else:
      above = init_array[i]

  # get below
  below = init_array[i - 1] if i - 1 >= 0 and init_array[i - 1] - n < 0 else None

  print('---------')
  print('array is', init_array)
  print('given x is', n)
  print('below is', below)
  print('above is', above)
  print('appending', numbers_to_append[_])

  # check if number trying to append doesn't exists in array
  # WARNING: this check may be slow and added only for showing that we need to add only unique numbers
  # real check should be based on real conditions and numbers that we suppose to add
  i_to_append = b.bisect_left(init_array, numbers_to_append[_])
  if numbers_to_append[_] not in init_array[i_to_append:i_to_append+1]:
    init_array.insert(i_to_append, numbers_to_append[_])

output:

---------
array is [1, 5, 7, 9, 10, 11, 14, 15]
given x is 12
below is 11
above is 14
appending 22
---------
array is [1, 5, 7, 9, 10, 11, 14, 15, 22]
given x is 55
below is 22
above is None
appending 4
---------
array is [1, 4, 5, 7, 9, 10, 11, 14, 15, 22]
given x is 23
below is 22
above is None
appending 12
---------
array is [1, 4, 5, 7, 9, 10, 11, 12, 14, 15, 22]
given x is 55
below is 22
above is None
appending 88
---------
array is [1, 4, 5, 7, 9, 10, 11, 12, 14, 15, 22, 88]
given x is 0
below is None
above is 1
appending 99
---------
array is [1, 4, 5, 7, 9, 10, 11, 12, 14, 15, 22, 88, 99]
given x is 55
below is 22
above is 88
appending 109
---------
array is [1, 4, 5, 7, 9, 10, 11, 12, 14, 15, 22, 88, 99, 109]
given x is 10
below is 9
above is 11
appending 999
---------
array is [1, 4, 5, 7, 9, 10, 11, 12, 14, 15, 22, 88, 99, 109, 999]
given x is 999
below is 109
above is None
appending 1000
rzlvmp
  • 7,512
  • 5
  • 16
  • 45
  • `Surely you're not serious about` → @KellyBundy yep. some kind of dummy check to explain that numbers to add must be unique. Because I don't know real conditions and how often we need to add new numbers – rzlvmp Apr 14 '22 at 02:21
  • `My point is that that's very slow` → Do you mean `not in init_array` place? Yep, that is. I added comment about this check – rzlvmp Apr 14 '22 at 02:28
  • @KellyBundy Sure. But that is a different task that depends on specific (unknown) conditions. In my case the faster way will be removing `if` condition at all. Because `numbers_to_append` are unique by design (however let me use your hint ;) ) – rzlvmp Apr 14 '22 at 02:44