@ sancho.s Ładnie zauważony. Chociaż odpowiedzi na to pytanie są o wiele lepsze niż te na inne pytanie. Więc zamierzam głosować za zamknięciem drugiego jako duplikatu tego.
Jean-François Corbett
Odpowiedzi:
326
Jeśli nie jesteśmy pewni, że lista jest sortowana, możemy skorzystać z wbudowanej min()funkcji , aby znaleźć element, który ma minimalną odległość od określonego numeru.
>>> min(myList, key=lambda x:abs(x-myNumber))4
Zauważ, że działa również z dyktami z kluczami int, takimi jak {1: "a", 2: "b"}. Ta metoda zajmuje O (n) czasu.
Jeśli lista jest już posortowana lub możesz zapłacić cenę za sortowanie tablicy tylko raz, użyj metody bisekcji zilustrowanej w odpowiedzi @ Lauritza, która zajmuje tylko O (log n) czasu (zwróć jednak uwagę, że sprawdzenie, czy lista jest już posortowana, to O (n) a sortowanie to O (n log n).)
co powiesz również na zwrócenie indeksu, że to się stało na liście?
Charlie Parker,
@CharlieParker Stwórz własną implementację min, uruchom ją za pomocą Dictionary ( items()) zamiast listy i zwróć klucz zamiast wartości na końcu.
Dustin Oprea
2
Lub użyj numpy.argminzamiast, minaby uzyskać indeks zamiast wartości.
148
Zmienię nazwę funkcji, take_closestaby była zgodna z konwencjami nazewnictwa PEP8.
Jeśli masz na myśli szybkie wykonanie w przeciwieństwie do szybkiego zapisu, niemin powinno być Twoją ulubioną bronią, z wyjątkiem jednego bardzo wąskiego przypadku użycia. Rozwiązanie musi zbadać każdy numer na liście i wykonać obliczenia dla każdego numeru. Używanie zamiast tego jest prawie zawsze szybsze.minbisect.bisect_left
„Prawie” wynika z faktu, że bisect_leftlista musi być posortowana, aby działała. Mamy nadzieję, że Twój przypadek użycia jest taki, że możesz raz posortować listę, a następnie zostawić ją w spokoju. Nawet jeśli nie, o ile nie musisz sortować danych przed każdym połączeniem take_closest, bisectmoduł prawdopodobnie zajmie pierwsze miejsce. Jeśli masz wątpliwości, spróbuj obu i spójrz na różnicę w świecie rzeczywistym.
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
Bisect działa na zasadzie wielokrotnego dzielenia listy na pół i sprawdzania, która połowa myNumbermusi się znaleźć, patrząc na środkową wartość. Oznacza to, że ma czas pracy O (log n) , w przeciwieństwie do O (n) czas pracującym Najwyżej oceniane odpowiedź . Jeśli porównamy te dwie metody i podamy obie z posortowanymi myListwynikami, oto wyniki:
$ python -m timeit -s "
z najbliższego importu take_closest
z losowego importu randint
a = zakres (-1000, 1000, 10) "" take_closest (a, randint (-1100, 1100)) "
100000 pętli, najlepiej 3: 2,22 usek na pętlę
$ python -m timeit -s "
z najbliższego importu z_min
z losowego importu randint
a = zakres (-1000, 1000, 10) "" with_min (a, randint (-1100, 1100)) "
10000 pętli, najlepiej 3: 43,9 usek na pętlę
Więc w tym konkretnym teście bisectjest prawie 20 razy szybszy. W przypadku dłuższych list różnica będzie większa.
A co, jeśli wyrównamy szanse, usuwając warunek wstępny, który myListnależy załatwić? Powiedzmy, że po każdymtake_closest wywołaniu sortujemy kopię listy , pozostawiając minniezmienione rozwiązanie. Korzystając z listy 200 pozycji w powyższym teście, bisectrozwiązanie jest nadal najszybsze, choć tylko o około 30%.
Jest to dziwny wynik, biorąc pod uwagę, że krok sortowania to O (n log (n)) ! Jedynym powodem, dla którego minwciąż przegrywa, jest to, że sortowanie odbywa się w wysoce zoptymalizowanym kodzie c, podczas gdy mintrzeba wlecieć w wywołanie funkcji lambda dla każdego elementu. Wraz ze myListwzrostem rozmiaru minrozwiązanie ostatecznie będzie szybsze. Zauważ, że musieliśmy ułożyć wszystko na jego korzyść, aby minrozwiązanie wygrało.
Samo sortowanie wymaga O (N log N), więc będzie wolniejsze, gdy N stanie się duże. Na przykład, jeśli używasz a=range(-1000,1000,2);random.shuffle(a), przekonasz się, że takeClosest(sorted(a), b)stanie się to wolniejsze.
kennytm
3
@KennyTM Przyznam ci to i wskażę to w mojej odpowiedzi. Ale tak długo, jak getClosestmożna wywołać więcej niż jeden raz dla każdego rodzaju, będzie to szybsze, aw przypadku użycia jednorazowego sortowania jest to oczywiste.
Lauritz V. Thaulow
co powiesz również na zwrócenie indeksu, że to się stało na liście?
Charlie Parker,
Jeśli myListjuż jest, np.arrayto użycie np.searchsortedzamiast bisectjest szybsze.
Lambda jest specjalny sposób pisania funkcję „anonimowy” (funkcja, która nie ma nazwy). Możesz przypisać mu dowolną nazwę, ponieważ lambda jest wyrażeniem.
! Niepoprawnie! Powinien być if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];. Lepiej jednak zapisz tę wartość wcześniej.
lk_vc
Z pewnością funkcja w obecnej postaci zwraca już indeks najbliższego. Aby spełniał wymagania PO, druga ostatnia linia nie powinna być czytana najbliżej = myList [i]
Paula Livingstone
2
Ważne jest, aby zauważyć, że pomysł Lauritza dotyczący użycia połowy w rzeczywistości nie znajduje najbliższej wartości w MyList do MyNumber. Zamiast tego funkcja bisect znajduje następną wartość w kolejności po MyNumber w MyList. Tak więc w przypadku OP w rzeczywistości zwrócona zostanie pozycja 44 zamiast pozycji 4.
Funkcja Lauritza działa poprawnie. Używasz tylko bisect_left, ale Lauritz zasugerował funkcję takeClosest (...), która wykonuje dodatkowe sprawdzenie.
Kanat
Jeśli zamierzasz używać NumPy, możesz użyć np.searchsortedzamiast bisect_left. I @Kanat ma rację - za rozwiązanie Lauritz robi to kod, który rozgrywki, który z dwóch kandydatów jest bliżej.
John Y
1
Rozszerzając odpowiedź Gustavo Limy. To samo można zrobić bez tworzenia zupełnie nowej listy. Wartości na liście można zastąpić różnicami w miarę FORpostępu pętli.
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
Odpowiedzi:
Jeśli nie jesteśmy pewni, że lista jest sortowana, możemy skorzystać z wbudowanej
min()
funkcji , aby znaleźć element, który ma minimalną odległość od określonego numeru.Zauważ, że działa również z dyktami z kluczami int, takimi jak
{1: "a", 2: "b"}
. Ta metoda zajmuje O (n) czasu.Jeśli lista jest już posortowana lub możesz zapłacić cenę za sortowanie tablicy tylko raz, użyj metody bisekcji zilustrowanej w odpowiedzi @ Lauritza, która zajmuje tylko O (log n) czasu (zwróć jednak uwagę, że sprawdzenie, czy lista jest już posortowana, to O (n) a sortowanie to O (n log n).)
źródło
O(n)
, że trochę hakowaniabisect
da ci ogromną poprawęO(log n)
(jeśli twoja tablica wejściowa jest posortowana).min
, uruchom ją za pomocą Dictionary (items()
) zamiast listy i zwróć klucz zamiast wartości na końcu.numpy.argmin
zamiast,min
aby uzyskać indeks zamiast wartości.Zmienię nazwę funkcji,
take_closest
aby była zgodna z konwencjami nazewnictwa PEP8.Jeśli masz na myśli szybkie wykonanie w przeciwieństwie do szybkiego zapisu, nie
min
powinno być Twoją ulubioną bronią, z wyjątkiem jednego bardzo wąskiego przypadku użycia. Rozwiązanie musi zbadać każdy numer na liście i wykonać obliczenia dla każdego numeru. Używanie zamiast tego jest prawie zawsze szybsze.min
bisect.bisect_left
„Prawie” wynika z faktu, że
bisect_left
lista musi być posortowana, aby działała. Mamy nadzieję, że Twój przypadek użycia jest taki, że możesz raz posortować listę, a następnie zostawić ją w spokoju. Nawet jeśli nie, o ile nie musisz sortować danych przed każdym połączeniemtake_closest
,bisect
moduł prawdopodobnie zajmie pierwsze miejsce. Jeśli masz wątpliwości, spróbuj obu i spójrz na różnicę w świecie rzeczywistym.Bisect działa na zasadzie wielokrotnego dzielenia listy na pół i sprawdzania, która połowa
myNumber
musi się znaleźć, patrząc na środkową wartość. Oznacza to, że ma czas pracy O (log n) , w przeciwieństwie do O (n) czas pracującym Najwyżej oceniane odpowiedź . Jeśli porównamy te dwie metody i podamy obie z posortowanymimyList
wynikami, oto wyniki:Więc w tym konkretnym teście
bisect
jest prawie 20 razy szybszy. W przypadku dłuższych list różnica będzie większa.A co, jeśli wyrównamy szanse, usuwając warunek wstępny, który
myList
należy załatwić? Powiedzmy, że po każdymtake_closest
wywołaniu sortujemy kopię listy , pozostawiającmin
niezmienione rozwiązanie. Korzystając z listy 200 pozycji w powyższym teście,bisect
rozwiązanie jest nadal najszybsze, choć tylko o około 30%.Jest to dziwny wynik, biorąc pod uwagę, że krok sortowania to O (n log (n)) ! Jedynym powodem, dla którego
min
wciąż przegrywa, jest to, że sortowanie odbywa się w wysoce zoptymalizowanym kodzie c, podczas gdymin
trzeba wlecieć w wywołanie funkcji lambda dla każdego elementu. Wraz zemyList
wzrostem rozmiarumin
rozwiązanie ostatecznie będzie szybsze. Zauważ, że musieliśmy ułożyć wszystko na jego korzyść, abymin
rozwiązanie wygrało.źródło
a=range(-1000,1000,2);random.shuffle(a)
, przekonasz się, żetakeClosest(sorted(a), b)
stanie się to wolniejsze.getClosest
można wywołać więcej niż jeden raz dla każdego rodzaju, będzie to szybsze, aw przypadku użycia jednorazowego sortowania jest to oczywiste.myList
już jest,np.array
to użycienp.searchsorted
zamiastbisect
jest szybsze.Lambda jest specjalny sposób pisania funkcję „anonimowy” (funkcja, która nie ma nazwy). Możesz przypisać mu dowolną nazwę, ponieważ lambda jest wyrażeniem.
„Długie” zapisanie powyższego to:
źródło
Ten kod daje indeks najbliższej liczby numerów na liście.
Rozwiązanie podane przez KennyTM jest ogólnie najlepsze, ale w przypadkach, gdy nie możesz z niego skorzystać (jak brython), ta funkcja załatwi sprawę
źródło
Powtarzaj listę i porównaj aktualną najbliższą liczbę z
abs(currentNumber - myNumber)
:źródło
if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];
. Lepiej jednak zapisz tę wartość wcześniej.Ważne jest, aby zauważyć, że pomysł Lauritza dotyczący użycia połowy w rzeczywistości nie znajduje najbliższej wartości w MyList do MyNumber. Zamiast tego funkcja bisect znajduje następną wartość w kolejności po MyNumber w MyList. Tak więc w przypadku OP w rzeczywistości zwrócona zostanie pozycja 44 zamiast pozycji 4.
Aby uzyskać wartość najbliższą 5, możesz spróbować przekonwertować listę na tablicę i użyć argmin z numpy w ten sposób.
Nie wiem, jak szybko to by się stało, przypuszczam, że to „niezbyt”.
źródło
np.searchsorted
zamiastbisect_left
. I @Kanat ma rację - za rozwiązanie Lauritz robi to kod, który rozgrywki, który z dwóch kandydatów jest bliżej.Rozszerzając odpowiedź Gustavo Limy. To samo można zrobić bez tworzenia zupełnie nowej listy. Wartości na liście można zastąpić różnicami w miarę
FOR
postępu pętli.źródło
Jeśli mogę dodać do odpowiedzi @ Lauritza
Aby uniknąć błędu uruchamiania, nie zapomnij dodać warunku przed
bisect_left
wierszem:więc pełny kod będzie wyglądał następująco:
źródło