@ sancho.s Красиво замечен. Хотя ответы на этот вопрос намного лучше, чем ответы на этот другой вопрос. Так что я собираюсь голосовать, чтобы закрыть другой как дубликат этого.
Жан-Франсуа Корбетт
Ответы:
327
Если мы не уверены, что список отсортирован, мы могли бы использовать встроенную 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).)
а как насчет возвращения индекса, что это произошло в списке?
Чарли Паркер
@CharlieParker Создайте свою собственную реализацию min, запустите ее над словарем ( items()) вместо списка и верните ключ вместо значения в конце.
Дастин Опря
2
Или используйте numpy.argminвместо того, minчтобы получить индекс вместо значения.
148
Я переименую функцию 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решение победило.
Сама сортировка требует O (N log N), поэтому она будет медленнее, когда N становится большим. Например, если вы используете, a=range(-1000,1000,2);random.shuffle(a)вы обнаружите, takeClosest(sorted(a), b)что станет медленнее.
Kennytm
3
@KennyTM Я дам вам это и укажу на это в своем ответе. Но так как long getClosestможет вызываться более одного раза для каждого сорта, это будет быстрее, а для варианта с однократной сортировкой это не составит труда.
Лауриц В. Таулов
а как насчет возвращения индекса, что это произошло в списке?
Чарли Паркер
Если myListэто уже, np.arrayто использование np.searchsortedвместо bisectбыстрее.
Лямбда особый способ написания «анонимную» функцию (функция , которая не имеет названия). Вы можете назначить ему любое имя, которое хотите, потому что лямбда - это выражение.
Однако обратите внимание, что присвоение лямбды именам не рекомендуется в соответствии с PEP 8 .
Эверт Хейлен,
6
def closest(list,Number):
aux =[]for valor in list:
aux.append(abs(Number-valor))return aux.index(min(aux))
Этот код даст вам индекс ближайшего номера числа в списке.
Решение, данное KennyTM, является лучшим в целом, но в тех случаях, когда вы не можете его использовать (например, brython), эта функция будет работать
! Неверно! Должно быть if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];. Хотя лучше сохранить это значение заранее.
lk_vc
Конечно, функция в том виде, в каком она есть, уже возвращает индекс ближайшего. Для того, чтобы он соответствовал требованиям ОП, не следует читать вторую последнюю строку: closest = myList [i]
Паула Ливингстон
2
Важно отметить, что идея предложения Лаурица об использовании bisect на самом деле не находит в MyList значение, наиболее близкое к MyNumber. Вместо этого bisect находит следующее значение по порядку после MyNumber в MyList. Таким образом, в случае OP вы фактически вернули бы позицию 44 вместо позиции 4.
Функция Лорица работает правильно. Вы используете только bisect_left, но Lauritz предложил функцию takeClosest (...), которая делает дополнительную проверку.
Канат
Если вы собираетесь использовать NumPy, вы можете использовать np.searchsortedвместо bisect_left. И @Kanat прав - решение Lauritz включает в себя код, который выбирает, какой из двух кандидатов ближе.
Джон Y
1
Расширяя ответ Густаво Лимы. То же самое можно сделать без создания совершенно нового списка. Значения в списке могут быть заменены дифференциалами в ходе 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 =5print(f_ClosestVal(myList, v_Num))## Gives "3," the index of the first "4" in the list.
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]):returnFalse
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
Ответы:
Если мы не уверены, что список отсортирован, мы могли бы использовать встроенную
min()
функцию , чтобы найти элемент, который имеет минимальное расстояние от указанного числа.Обратите внимание, что он также работает с 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()
) вместо списка и верните ключ вместо значения в конце.numpy.argmin
вместо того,min
чтобы получить индекс вместо значения.Я переименую функцию
take_closest
в соответствии с соглашениями об именах PEP8.Если вы имеете в виду «быстрое выполнение», а не «быстрое написание», оно не
min
должно быть вашим предпочтением, за исключением одного очень узкого варианта использования. Решение необходимо изучить каждый номер в списке и сделать расчет для каждого номера. Использование вместо этого почти всегда быстрее.min
bisect.bisect_left
«Почти» происходит из-за того, что
bisect_left
список должен быть отсортирован для работы. Надеюсь, ваш вариант использования таков, что вы можете отсортировать список один раз, а затем оставить его в покое. Даже если это не так, если вам не нужно выполнять сортировку перед каждым вызовомtake_closest
,bisect
модуль, скорее всего, выйдет на первое место. Если вы сомневаетесь, попробуйте оба и посмотрите на разницу в реальном мире.Бисект работает, многократно сокращая список пополам и выясняя, какая половина
myNumber
должна быть в списке , посмотрев на среднее значение. Это означает, что он имеет время выполнения O (log n), а не время O (n) ответа с наибольшим количеством голосов . Если мы сравним два метода и предложим оба отсортированныхmyList
, это результаты:Так что в этом конкретном тесте,
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
быстрее.Лямбда особый способ написания «анонимную» функцию (функция , которая не имеет названия). Вы можете назначить ему любое имя, которое хотите, потому что лямбда - это выражение.
«Длинный» способ написания вышеупомянутого будет:
источник
Этот код даст вам индекс ближайшего номера числа в списке.
Решение, данное KennyTM, является лучшим в целом, но в тех случаях, когда вы не можете его использовать (например, brython), эта функция будет работать
источник
Переберите список и сравните текущий ближайший номер с
abs(currentNumber - myNumber)
:источник
if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];
. Хотя лучше сохранить это значение заранее.Важно отметить, что идея предложения Лаурица об использовании bisect на самом деле не находит в MyList значение, наиболее близкое к MyNumber. Вместо этого bisect находит следующее значение по порядку после MyNumber в MyList. Таким образом, в случае OP вы фактически вернули бы позицию 44 вместо позиции 4.
Чтобы получить значение, которое ближе всего к 5, вы можете попытаться преобразовать список в массив и использовать argmin из numpy, вот так.
Я не знаю, как быстро это будет, хотя, я думаю, будет "не очень".
источник
np.searchsorted
вместоbisect_left
. И @Kanat прав - решение Lauritz включает в себя код, который выбирает, какой из двух кандидатов ближе.Расширяя ответ Густаво Лимы. То же самое можно сделать без создания совершенно нового списка. Значения в списке могут быть заменены дифференциалами в ходе
FOR
цикла.источник
Если я могу добавить к ответу @ Lauritz
Чтобы не было ошибки запуска, не забудьте добавить условие перед
bisect_left
строкой:поэтому полный код будет выглядеть так:
источник