Давши список цілих чисел, я хочу знайти, яке число є найближчим до числа, яке я ввожу:
>>> 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
Зауважте, що він також працює з диктами з клавішами int, наприклад {1: "a", 2: "b"}. Цей метод займає O (n) час.
Якщо список вже відсортований, або ви можете заплатити ціну сортування масиву лише один раз, використовуйте метод розбиття, проілюстрований у відповіді @ Лауріца, який займає лише час 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 від випадкового ранжиру на імпорт a = діапазон (-1000, 1000, 10) "" take_closest (a, randint (-1100, 1100)) " 100000 петель, найкраще 3: 2,22 Usec на цикл $ python -m timeit -s " від найближчого імпорту з_min від випадкового ранжиру на імпорт a = діапазон (-1000, 1000, 10) "" з_min (a, randint (-1100, 1100)) " 10000 петель, найкраще 3: 43,9 Usec за петлю
Тож у цьому конкретному тесті 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];. Краще зберігайте цю вартість заздалегідь.
Важливо зауважити, що ідея навіювання Лауріца щодо використання бісектії насправді не знаходить найближчого значення в MyList для MyNumber. Натомість бісект знаходить наступне значення для порядку після MyNumber у MyList. Тож у випадку з ОП ви фактично отримаєте позицію 44 повернуто замість позиції 4.
>>> myList = [1, 3, 4, 44, 88]
>>> myNumber = 5
>>> pos = (bisect_left(myList, myNumber))
>>> myList[pos]
...
44
Щоб отримати значення, найближче до 5, ви можете спробувати перетворити список в масив і використовувати аргумент з 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.
Якщо я можу додати відповідь @ Лауріца
Щоб не мати помилки запуску, не забудьте додати умову перед 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