Учитывая список целых чисел, я хочу найти, какое число является самым близким к числу, которое я даю во входных данных:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
Есть ли быстрый способ сделать это?
Учитывая список целых чисел, я хочу найти, какое число является самым близким к числу, которое я даю во входных данных:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
Есть ли быстрый способ сделать это?
Ответы:
Если мы не уверены, что список отсортирован, мы могли бы использовать встроенную min()функцию , чтобы найти элемент, который имеет минимальное расстояние от указанного числа.
>>> min(myList, key=lambda x:abs(x-myNumber))
4
Обратите внимание, что он также работает с dicts с ключами типа int, например {1: "a", 2: "b"}. Этот метод занимает O (n) времени.
Если список уже отсортирован, или вы можете заплатить цену сортировки массива только один раз, используйте метод деления пополам, показанный в ответе @ Lauritz, который занимает всего O (log n) времени (однако обратите внимание, что проверка, если список уже отсортирован, равна O (n) и сортировка O (n log n).)
O(n), где небольшой взлом bisectдаст вам значительное улучшение O(log n)(если ваш входной массив отсортирован).
min, запустите ее над словарем ( items()) вместо списка и верните ключ вместо значения в конце.
Я переименую функцию take_closestв соответствии с соглашениями об именах PEP8.
Если вы имеете в виду «быстрое выполнение», а не «быстрое написание», оно неmin должно быть вашим предпочтением, за исключением одного очень узкого варианта использования. Решение необходимо изучить каждый номер в списке и сделать расчет для каждого номера. Использование вместо этого почти всегда быстрее.minbisect.bisect_left
«Почти» происходит из-за того, что bisect_leftсписок должен быть отсортирован для работы. Надеюсь, ваш вариант использования таков, что вы можете отсортировать список один раз, а затем оставить его в покое. Даже если это не так, если вам не нужно выполнять сортировку перед каждым вызовом take_closest, bisectмодуль, скорее всего, выйдет на первое место. Если вы сомневаетесь, попробуйте оба и посмотрите на разницу в реальном мире.
from bisect import bisect_left
def take_closest(myList, myNumber):
"""
Assumes myList is sorted. Returns closest value to myNumber.
If two numbers are equally close, return the smallest number.
"""
pos = bisect_left(myList, myNumber)
if pos == 0:
return myList[0]
if pos == len(myList):
return myList[-1]
before = myList[pos - 1]
after = myList[pos]
if after - myNumber < myNumber - before:
return after
else:
return before
Бисект работает, многократно сокращая список пополам и выясняя, какая половина myNumberдолжна быть в списке , посмотрев на среднее значение. Это означает, что он имеет время выполнения O (log n), а не время O (n) ответа с наибольшим количеством голосов . Если мы сравним два метода и предложим оба отсортированных myList, это результаты:
$ python -m timeit -s " из ближайшего импорта take_closest от случайного импорта randint a = range (-1000, 1000, 10) "" take_closest (a, randint (-1100, 1100)) " 100000 циклов, лучшее из 3: 2,22 пользователя на цикл $ python -m timeit -s " из ближайшего импорта with_min от случайного импорта randint a = range (-1000, 1000, 10) "" with_min (a, randint (-1100, 1100)) " 10000 петель, лучшее из 3: 43,9 мксек на петлю
Так что в этом конкретном тесте, bisectэто почти в 20 раз быстрее. Для более длинных списков разница будет больше.
Что если мы выровняем игровое поле, удалив предварительное условие, которое myListдолжно быть отсортировано? Допустим, мы сортируем копию списка каждый раз, когда take_closest вызывается, оставляя minрешение без изменений. Используя список из 200 пунктов в приведенном выше тесте, bisectрешение все еще остается самым быстрым, хотя только на 30%.
Это странный результат, учитывая, что шаг сортировки O (n log (n)) ! Единственная причина, minкоторая все еще проигрывает, заключается в том, что сортировка выполняется в высокооптимизированном коде c, в то время как minдля каждого элемента приходится использовать лямбда-функцию. По мере myListувеличения размера minрешение в конечном итоге будет быстрее. Обратите внимание, что нам пришлось сложить все в свою пользу, чтобы minрешение победило.
a=range(-1000,1000,2);random.shuffle(a)вы обнаружите, takeClosest(sorted(a), b)что станет медленнее.
getClosestможет вызываться более одного раза для каждого сорта, это будет быстрее, а для варианта с однократной сортировкой это не составит труда.
myListэто уже, np.arrayто использование np.searchsortedвместо bisectбыстрее.
>>> takeClosest = lambda num,collection:min(collection,key=lambda x:abs(x-num))
>>> takeClosest(5,[4,1,88,44,3])
4
Лямбда особый способ написания «анонимную» функцию (функция , которая не имеет названия). Вы можете назначить ему любое имя, которое хотите, потому что лямбда - это выражение.
«Длинный» способ написания вышеупомянутого будет:
def takeClosest(num,collection):
return min(collection,key=lambda x:abs(x-num))
def closest(list, Number):
aux = []
for valor in list:
aux.append(abs(Number-valor))
return aux.index(min(aux))
Этот код даст вам индекс ближайшего номера числа в списке.
Решение, данное KennyTM, является лучшим в целом, но в тех случаях, когда вы не можете его использовать (например, brython), эта функция будет работать
Переберите список и сравните текущий ближайший номер с abs(currentNumber - myNumber):
def takeClosest(myList, myNumber):
closest = myList[0]
for i in range(1, len(myList)):
if abs(i - myNumber) < closest:
closest = i
return closest
if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];. Хотя лучше сохранить это значение заранее.
Важно отметить, что идея предложения Лаурица об использовании bisect на самом деле не находит в MyList значение, наиболее близкое к MyNumber. Вместо этого bisect находит следующее значение по порядку после MyNumber в MyList. Таким образом, в случае OP вы фактически вернули бы позицию 44 вместо позиции 4.
>>> myList = [1, 3, 4, 44, 88]
>>> myNumber = 5
>>> pos = (bisect_left(myList, myNumber))
>>> myList[pos]
...
44
Чтобы получить значение, которое ближе всего к 5, вы можете попытаться преобразовать список в массив и использовать argmin из numpy, вот так.
>>> import numpy as np
>>> myNumber = 5
>>> myList = [1, 3, 4, 44, 88]
>>> myArray = np.array(myList)
>>> pos = (np.abs(myArray-myNumber)).argmin()
>>> myArray[pos]
...
4
Я не знаю, как быстро это будет, хотя, я думаю, будет "не очень".
np.searchsortedвместо bisect_left. И @Kanat прав - решение Lauritz включает в себя код, который выбирает, какой из двух кандидатов ближе.
Расширяя ответ Густаво Лимы. То же самое можно сделать без создания совершенно нового списка. Значения в списке могут быть заменены дифференциалами в ходе FORцикла.
def f_ClosestVal(v_List, v_Number):
"""Takes an unsorted LIST of INTs and RETURNS INDEX of value closest to an INT"""
for _index, i in enumerate(v_List):
v_List[_index] = abs(v_Number - i)
return v_List.index(min(v_List))
myList = [1, 88, 44, 4, 4, -2, 3]
v_Num = 5
print(f_ClosestVal(myList, v_Num)) ## Gives "3," the index of the first "4" in the list.
Если я могу добавить к ответу @ Lauritz
Чтобы не было ошибки запуска, не забудьте добавить условие перед bisect_leftстрокой:
if (myNumber > myList[-1] or myNumber < myList[0]):
return False
поэтому полный код будет выглядеть так:
from bisect import bisect_left
def takeClosest(myList, myNumber):
"""
Assumes myList is sorted. Returns closest value to myNumber.
If two numbers are equally close, return the smallest number.
If number is outside of min or max return False
"""
if (myNumber > myList[-1] or myNumber < myList[0]):
return False
pos = bisect_left(myList, myNumber)
if pos == 0:
return myList[0]
if pos == len(myList):
return myList[-1]
before = myList[pos - 1]
after = myList[pos]
if after - myNumber < myNumber - before:
return after
else:
return before