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

소스 코드: Lib/bisect.py

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

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

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

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

  The returned insertion point i partitions the array a into two halves so that `all(val < x for val in a[lo : i])` for the left side and `all(val >= x for val in a[i : hi])` for the right side.

  key specifies a key function of one argument that is used to extract a comparison key from each element in the array. To support searching complex records, the key function is not applied to the x value.

  If key is `None`, the elements are compared directly with no intervening function call.

  버전 3.10에서 변경: Added the key parameter.

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

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

  The returned insertion point i partitions the array a into two halves so that `all(val <= x for val in a[lo : i])` for the left side and `all(val > x for val in a[i : hi])` for the right side.

  key specifies a key function of one argument that is used to extract a comparison key from each element in the array. To support searching complex records, the key function is not applied to the x value.

  If key is `None`, the elements are compared directly with no intervening function call.

  버전 3.10에서 변경: Added the key parameter.

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

  Insert x in a in sorted order.

  This function first runs `bisect_left()` to locate an insertion point. Next, it runs the `insert()` method on a to insert x at the appropriate position to maintain sort order.

  To support inserting records in a table, the key function (if any) is applied to x for the search step but not for the insertion step.

  Keep in mind that the `O(log n)` search is dominated by the slow O(n) insertion step.

  버전 3.10에서 변경: Added the key parameter.

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

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

  This function first runs `bisect_right()` to locate an insertion point. Next, it runs the `insert()` method on a to insert x at the appropriate position to maintain sort order.

  To support inserting records in a table, the key function (if any) is applied to x for the search step but not for the insertion step.

  Keep in mind that the `O(log n)` search is dominated by the slow O(n) insertion step.

  버전 3.10에서 변경: Added the key parameter.

## Performance Notes

When writing time sensitive code using bisect() and insort(), keep these thoughts in mind:

- Bisection is effective for searching ranges of values. For locating specific values, dictionaries are more performant.

- The insort() functions are `O(n)` because the logarithmic search step is dominated by the linear time insertion step.

- The search functions are stateless and discard key function results after they are used. Consequently, if the search functions are used in a loop, the key function may be called again and again on the same array elements. If the key function isn’t fast, consider wrapping it with `functools.cache()` to avoid duplicate computations. Alternatively, consider searching an array of precomputed keys to locate the insertion point (as shown in the examples section below).

더 보기

- Sorted Collections is a high performance module that uses bisect to managed sorted collections of data.

- The SortedCollection recipe uses bisect to build a full-featured collection class with straight-forward search methods and support for a key-function. The keys are precomputed to save unnecessary calls to the key function during searches.

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

위의 `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

## Examples

`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']

The `bisect()` and `insort()` functions also work with lists of tuples. The key argument can serve to extract the field used for ordering records in a table:

    >>> from collections import namedtuple
    >>> from operator import attrgetter
    >>> from bisect import bisect, insort
    >>> from pprint import pprint

    >>> Movie = namedtuple('Movie', ('name', 'released', 'director'))

    >>> movies = [
    ...     Movie('Jaws', 1975, 'Speilberg'),
    ...     Movie('Titanic', 1997, 'Cameron'),
    ...     Movie('The Birds', 1963, 'Hitchcock'),
    ...     Movie('Aliens', 1986, 'Scott')
    ... ]

    >>> # Find the first movie released after 1960
    >>> by_year = attrgetter('released')
    >>> movies.sort(key=by_year)
    >>> movies[bisect(movies, 1960, key=by_year)]
    Movie(name='The Birds', released=1963, director='Hitchcock')

    >>> # Insert a movie while maintaining sort order
    >>> romance = Movie('Love Story', 1970, 'Hiller')
    >>> insort(movies, romance, key=by_year)
    >>> pprint(movies)
    [Movie(name='The Birds', released=1963, director='Hitchcock'),
     Movie(name='Love Story', released=1970, director='Hiller'),
     Movie(name='Jaws', released=1975, director='Speilberg'),
     Movie(name='Aliens', released=1986, director='Scott'),
     Movie(name='Titanic', released=1997, director='Cameron')]

If the key function is expensive, it is possible to avoid repeated function calls by searching a list of precomputed keys to find the index of a record:

    >>> data = [('red', 5), ('blue', 1), ('yellow', 8), ('black', 0)]
    >>> data.sort(key=lambda r: r[1])       # Or use operator.itemgetter(1).
    >>> keys = [r[1] for r in data]         # Precompute a 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)