정수 목록에서 주어진 값에 가장 가까운 숫자를 얻습니다.


158

정수 목록이 주어지면 입력 한 숫자와 가장 가까운 숫자를 찾고 싶습니다.

>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4

이 작업을 수행하는 빠른 방법이 있습니까?


2
목록에서 발생한 색인을 반환하는 것은 어떻습니까?
찰리 파커


1
@ sancho.s 멋지게 발견되었습니다. 이 질문에 대한 답변은 다른 질문에 대한 답변보다 낫습니다. 저는 다른 하나를이 하나의 복제본으로 닫으려고 투표 할 것입니다.
Jean-François Corbett

답변:


326

목록이 정렬되어 있는지 확실하지 않으면 내장 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)입니다.)


13
복잡하게 말하면, 이것은 O(n)약간의 해킹으로 (입력 배열이 정렬 된 경우) bisect크게 향상 될 것 O(log n)입니다.
mic_e

5
@mic_e : 그건 단지 Lauritz의 대답 입니다.
kennytm

3
이 목록에서 발생한 인덱스를 반환하는 것은 어떻습니까?
찰리 파커

@CharlieParker의 구현을 직접 작성 하고 목록 대신 min사전 ( items()) 에서 실행 한 다음 값 대신 키를 반환하십시오.
더스틴 오프 레아

2
또는 numpy.argmin대신 min값을 사용하여 인덱스를 가져 오십시오.

148

take_closestPEP8 명명 규칙을 준수 하도록 함수의 이름을 바꾸겠습니다 .

빠른 쓰기와 달리 빠른 실행을 의미 하는 경우 하나의 매우 좁은 사용 사례를 제외하고는 선택한 무기 min아니 어야합니다 . min솔루션은리스트에있는 모든 수를 검토 할 필요가 각 번호에 대한 계산을한다. bisect.bisect_left대신 사용 하는 것이 거의 항상 빠릅니다.

"거의"는 bisect_left목록이 작동하도록 정렬되어야 한다는 사실에서 비롯됩니다 . 다행스럽게도 유스 케이스는 목록을 한 번 정렬 한 다음 그대로 두는 것이 좋습니다. 심지어하지 않을 경우, 같은 당신이 전화 할 때마다 전 종류에 필요하지 않는 take_closestbisect모듈 가능성이 정상에 올 것이다. 확실치 않은 경우 두 가지를 모두 시도하고 실제 차이를 살펴보십시오.

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_closestminbisect

정렬 단계가 O (n log (n)) 임을 고려하면 이상한 결과입니다 ! min정렬이 고도로 최적화 된 C 코드로 수행되는 동시에 min모든 항목에 대해 람다 함수를 호출해야하기 때문에 여전히 손실이 발생하는 유일한 이유 입니다 . 으로 myList크기에서 성장의 min솔루션은 결국 빨라집니다. 우리는 min솔루션이 승리하기 위해 모든 것을 유리하게 쌓아야했습니다 .


2
정렬 자체에는 O (N log N)가 필요하므로 N이 커지면 느려집니다. 예를 들어, 사용 a=range(-1000,1000,2);random.shuffle(a)하면 takeClosest(sorted(a), b)속도가 느려질 수 있습니다.
kennytm

3
@ KennyTM 나는 당신에게 그것을 부여하고 내 대답에 지적하겠습니다. 그러나 getClosest모든 정렬에 대해 한 번 이상 호출 될 수 있으면 더 빠르며 한 번만 사용하는 경우에는 그다지 쉬운 일이 아닙니다.
Lauritz V. Thaulow

이 목록에서 발생한 인덱스를 반환하는 것은 어떻습니까?
찰리 파커

myList이미 np.array사용 중인 경우 np.searchsorted대신 사용하는 bisect것이 더 빠릅니다.
Michael Hall

8
>>> 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))

2
그러나 람다를 이름에 할당하는 것은 PEP 8 에 따라 권장되지 않습니다 .
Evert Heylen

6
def closest(list, Number):
    aux = []
    for valor in list:
        aux.append(abs(Number-valor))

    return aux.index(min(aux))

이 코드는 목록에서 가장 가까운 숫자의 색인을 제공합니다.

KennyTM가 제공하는 솔루션이 전반적으로 가장 우수하지만 (브리 톤과 같이) 사용할 수없는 경우이 기능이 작동합니다.


5

목록을 반복하고 현재 가장 가까운 숫자를 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

1
인덱스를 반환 할 수도 있습니다.
찰리 파커

1
! 잘못되었습니다! 이어야 if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];합니다. 그래도 그 값을 미리 저장하는 것이 좋습니다.
lk_vc

분명히 함수는 이미 가장 가까운 인덱스를 반환합니다. 는 영업 이익의 요구 사항을 충족하기 위해해야 번째 마지막 줄에 가장 가까운 = myList에 [i]를 읽을 수 없습니다
폴라 리빙스톤

2

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

나는 이것이 얼마나 빠를 지 모른다. 나의 추측은 "별로 그렇지 않다".


2
Lauritz의 기능이 올바르게 작동합니다. bisect_left 만 사용했지만 Lauritz는 추가 검사를 수행하는 takeClosest (...) 함수를 제안했습니다.
카나 트

NumPy를 사용하려는 경우 np.searchsorted대신 대신 사용할 수 있습니다 bisect_left. 그리고 @Kanat이 옳습니다. Lauritz의 솔루션 에는 두 후보 중 더 가까운 것을 선택하는 코드 포함되어 있습니다.
John 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 = 5
print(f_ClosestVal(myList, v_Num)) ## Gives "3," the index of the first "4" in the list.

1

내가 추가 할 수 있다면 @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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.