정수 목록이 주어지면 입력 한 숫자와 가장 가까운 숫자를 찾고 싶습니다.
>>> 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 키와 같은 dicts 와도 작동합니다 {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
Bisect는 목록을 반복해서 반으로 줄이고 myNumber
중간 값을보고 절반 을 찾아야 합니다. 이는 가장 높은 투표 응답 의 O (n) 실행 시간 과 달리 O (log n) 의 실행 시간이 있음을 의미합니다 . 두 방법을 비교하고 둘 다 sorted로 제공 하면 다음과 같은 결과가 나타납니다.myList
$ python -m timeit -s " 가장 가까운 수입품에서 take_closest 무작위 수입 randint에서 a = 범위 (-1000, 1000, 10) ""take_closest (a, randint (-1100, 1100)) " 100000 루프, 최고 3 : 2 : 22 루프 당 루프 $ python -m timeit -s " with_min과 가장 가까운 수입 무작위 수입 randint에서 a = 범위 (-1000, 1000, 10) ""with_min (a, randint (-1100, 1100)) " 루프 당 10000 개의 루프, 3 : 3 : 43.9 usec
따라서이 특정 테스트에서는 bisect
거의 20 배 더 빠릅니다. 목록이 길수록 차이가 커집니다.
myList
정렬해야하는 전제 조건을 제거하여 경기장을 평평하게하면 어떻게 될까요? 솔루션이 변경되지 않은 채로 호출 될 때마다 목록의 사본을 정렬한다고 가정 해 봅시다 . 위 테스트에서 200 개 항목 목록을 사용하면 솔루션이 여전히 가장 빠르지 만 약 30 % 정도입니다.take_closest
min
bisect
정렬 단계가 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))
목록을 반복하고 현재 가장 가까운 숫자를 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 사용에 대한 Lauritz의 제안 아이디어가 MyList에서 MyNumber에 가장 가까운 값을 찾지 못한다는 점에 유의해야합니다. 대신, 양분이의 다음 값을 발견 하기 위해 myList에의 MyNumber 후를. 따라서 OP의 경우 실제로 4의 위치 대신 44의 위치가 반환됩니다.
>>> myList = [1, 3, 4, 44, 88]
>>> myNumber = 5
>>> pos = (bisect_left(myList, myNumber))
>>> myList[pos]
...
44
5에 가장 가까운 값을 얻으려면 목록을 배열로 변환하고 numpy의 argmin을 사용하십시오.
>>> 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