# `bisect` — 배열 이진 분할 알고리즘

소스 코드: Lib/bisect.py

이 모듈은 정렬된 리스트를 삽입 후에 다시 정렬할 필요 없도록 관리할 수 있도록 지원합니다. 값비싼 비교 연산이 포함된 항목의 긴 리스트의 경우, 이는 일반적인 방법에 비해 개선된 것입니다. 이 모듈은 기본적인 이진 분할 알고리즘을 사용하기 때문에 `bisect`라고 불립니다. 소스 코드는 알고리즘의 실제 예로서 가장 유용할 수 있습니다 (경계 조건은 이미 옳습니다!).

다음과 같은 함수가 제공됩니다:

`bisect.``bisect_left`(a, x, lo=0, hi=len(a))

  정렬된 순서를 유지하도록 a에 x를 삽입할 위치를 찾습니다. 매개 변수 lo 와 hi는 고려해야 할 리스트의 부분집합을 지정하는 데 사용될 수 있습니다; 기본적으로 전체 리스트가 사용됩니다. x가 a에 이미 있으면, 삽입 위치는 기존 항목 앞(왼쪽)이 됩니다. 반환 값은 a가 이미 정렬되었다고 가정할 때 `list.insert()`의 첫 번째 매개 변수로 사용하기에 적합합니다.

  반환된 삽입 위치 i는 배열 a를 이분하여, 왼쪽은 `all(val < x for val in a[lo:i])`, 오른쪽은 `all(val >= x for val in a[i:hi])`이 되도록 만듭니다.

`bisect.``bisect_right`(a, x, lo=0, hi=len(a))
`bisect.``bisect`(a, x, lo=0, hi=len(a))

  `bisect_left()`와 비슷하지만, a에 있는 x의 기존 항목 뒤(오른쪽)에 오는 삽입 위치를 반환합니다.

  반환된 삽입 위치 i는 배열 a를 이분하여, 왼쪽은 `all(val <= x for val in a[lo:i])`, 오른쪽은 `all(val > x for val in a[i:hi])`이 되도록 만듭니다.

`bisect.``insort_left`(a, x, lo=0, hi=len(a))

  a에 x를 정렬된 순서로 삽입합니다. a가 이미 정렬되었다고 가정할 때 `a.insert(bisect.bisect_left(a, x, lo, hi), x)`와 동등합니다. O(log n) 검색이 느린 O(n) 삽입 단계에 가려짐에 유념하십시오.

`bisect.``insort_right`(a, x, lo=0, hi=len(a))
`bisect.``insort`(a, x, lo=0, hi=len(a))

  `insort_left()`와 비슷하지만, a에 x를 x의 기존 항목 다음에 삽입합니다.

더 보기

bisect를 사용하여 직접적인 검색 메서드와 키 함수 지원을 포함하는 완전한 기능을 갖춘 컬렉션 클래스를 만드는 SortedCollection recipe. 검색 중에 불필요한 키 함수 호출을 피하고자 키는 미리 계산됩니다.

## 정렬된 리스트 검색하기

위의 `bisect()` 함수는 삽입 위치를 찾는 데 유용하지만, 일반적인 검색 작업에 사용하기가 까다롭거나 어색할 수 있습니다. 다음 다섯 함수는 정렬된 리스트에 대한 표준 조회로 변환하는 방법을 보여줍니다:

    def index(a, x):
        'Locate the leftmost value exactly equal to x'
        i = bisect_left(a, x)
        if i != len(a) and a[i] == x:
            return i
        raise ValueError

    def find_lt(a, x):
        'Find rightmost value less than x'
        i = bisect_left(a, x)
        if i:
            return a[i-1]
        raise ValueError

    def find_le(a, x):
        'Find rightmost value less than or equal to x'
        i = bisect_right(a, x)
        if i:
            return a[i-1]
        raise ValueError

    def find_gt(a, x):
        'Find leftmost value greater than x'
        i = bisect_right(a, x)
        if i != len(a):
            return a[i]
        raise ValueError

    def find_ge(a, x):
        'Find leftmost item greater than or equal to x'
        i = bisect_left(a, x)
        if i != len(a):
            return a[i]
        raise ValueError

## 다른 예제

`bisect()` 함수는 숫자 테이블 조회에 유용할 수 있습니다. 이 예제는 `bisect()`를 사용하여 (가령) 시험 점수에 대한 문자 등급을 조회하는데, 정렬된 숫자 경계점 집합에 기반합니다: 90 이상은 ‘A’, 80에서 89는 ‘B’ 등입니다:

    >>> def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
    ...     i = bisect(breakpoints, score)
    ...     return grades[i]
    ...
    >>> [grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]
    ['F', 'A', 'C', 'C', 'B', 'A', 'A']

`sorted()` 함수와 달리, `bisect()` 함수는 key 나 reversed 인자를 갖는 것은 의미가 없는데, 비효율적인 설계 (연속적인 bisect 함수 호출이 이전의 모든 키 조회를 “기억”하지 못합니다)를 초래하기 때문입니다.

대신, 해당 레코드의 인덱스를 찾기 위해 미리 계산된 키 리스트를 검색하는 것이 좋습니다:

    >>> data = [('red', 5), ('blue', 1), ('yellow', 8), ('black', 0)]
    >>> data.sort(key=lambda r: r[1])
    >>> keys = [r[1] for r in data]         # precomputed list of keys
    >>> data[bisect_left(keys, 0)]
    ('black', 0)
    >>> data[bisect_left(keys, 1)]
    ('blue', 1)
    >>> data[bisect_left(keys, 5)]
    ('red', 5)
    >>> data[bisect_left(keys, 8)]
    ('yellow', 8)