Учитывая список целых чисел, я хочу найти, какое число является самым близким к числу, которое я даю во входных данных:
>>> 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
должно быть вашим предпочтением, за исключением одного очень узкого варианта использования. Решение необходимо изучить каждый номер в списке и сделать расчет для каждого номера. Использование вместо этого почти всегда быстрее.min
bisect.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