정수 목록에서 주어진 값에 가장 가까운 숫자를 얻습니다.
정수 목록이 주어지면 입력 한 숫자와 가장 가까운 숫자를 찾고 싶습니다.
>>> 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)입니다.)
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
This is a strange result, considering that the sorting step is O(n log(n))! The only reason min
is still losing is that the sorting is done in highly optimalized c code, while min
has to plod along calling a lambda function for every item. As myList
grows in size, the min
solution will eventually be faster. Note that we had to stack everything in its favour for the min
solution to win.
>>> takeClosest = lambda num,collection:min(collection,key=lambda x:abs(x-num))
>>> takeClosest(5,[4,1,88,44,3])
4
A lambda is a special way of writing an "anonymous" function (a function that doesn't have a name). You can assign it any name you want because a lambda is an expression.
The "long" way of writing the the above would be:
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))
This code will give you the index of the closest number of Number in the list.
The solution given by KennyTM is the best overall, but in the cases you cannot use it (like brython), this function will do the work
Iterate over the list and compare the current closest number with 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
It's important to note that Lauritz's suggestion idea of using bisect does not actually find the closest value in MyList to MyNumber. Instead, bisect finds the next value in order after MyNumber in MyList. So in OP's case you'd actually get the position of 44 returned instead of the position of 4.
>>> myList = [1, 3, 4, 44, 88]
>>> myNumber = 5
>>> pos = (bisect_left(myList, myNumber))
>>> myList[pos]
...
44
To get the value that's closest to 5 you could try converting the list to an array and using argmin from numpy like so.
>>> 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
I don't know how fast this would be though, my guess would be "not very".
Expanding upon Gustavo Lima's answer. The same thing can be done without creating an entirely new list. The values in the list can be replaced with the differentials as the FOR
loop progresses.
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.
If I may add to @Lauritz's answer
In order not to have a run error don't forget to add a condition before the bisect_left
line:
if (myNumber > myList[-1] or myNumber < myList[0]):
return False
so the full code will look like:
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
'IT story' 카테고리의 다른 글
문자열의 첫 문자와 마지막 문자를 제거하는 방법 (0) | 2020.06.21 |
---|---|
오류 : gem을 실행하는 동안… (Errno :: EPERM) 작업이 허용되지 않음 (0) | 2020.06.21 |
C #에서 성과 이름의 첫 글자는 어떻게 대문자로 사용합니까? (0) | 2020.06.21 |
lodash를 사용하여 객체에서 정의되지 않은 null 값을 제거하는 방법은 무엇입니까? (0) | 2020.06.21 |
jQuery : .ready () 중에 문서 제목을 변경하는 방법은 무엇입니까? (0) | 2020.06.21 |