bisect --- 配列二分法アルゴリズム

ソースコード: Lib/bisect.py


このモジュールは、挿入のたびにリストをソートすることなく、ソートされた順序でリストを維持するためのサポートを提供します。 コストのかかる比較演算を伴う長いリストの場合、これは線形検索や頻繁な並べ替えよりも改善される可能性があります。

The module is called bisect because it uses a basic bisection algorithm to do its work. Unlike other bisection tools that search for a specific value, the functions in this module are designed to locate an insertion point. Accordingly, the functions never call an __eq__() method to determine whether a value has been found. Instead, the functions only call the __lt__() method and will return an insertion point between values in an array.

注釈

The functions in this module are not thread-safe. If multiple threads concurrently use bisect functions on the same sequence, this may result in undefined behaviour. Likewise, if the provided sequence is mutated by a different thread while a bisect function is operating on it, the result is undefined. For example, using insort_left() on the same list from multiple threads may result in the list becoming unsorted.

次の関数が用意されています:

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

ソートされた順序を保ったまま xa に挿入できる点を探し当てます。リストの中から検索する部分集合を指定するには、パラメータの lohi を使います。デフォルトでは、リスト全体が使われます。x がすでに a に含まれている場合、挿入点は既存のどのエントリーよりも前(左)になります。戻り値は、list.insert() の第一引数として使うのに適しています。a はすでにソートされているものとします。

返された挿入点 ip パーティションは、左のスライスでは all(elem < x for elem in a[lo : ip]) が真となり、右のスライスでは all(elem >= x for elem in a[ip : hi]) が真となるように、配列 a を2つのスライスに分割します。

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 and no key function is called.

バージョン 3.10 で変更: key パラメータが追加されました。

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 のうち、どのエントリーよりも後ろ(右)にくるような挿入点を返します。

返された挿入点 ip パーティションは、左のスライスでは all(elem <= x for elem in a[lo : ip]) が真となり、右のスライスでは all(elem > x for elem in a[ip : hi]) が真となるように、配列 a を2つのスライスに分割します。

バージョン 3.10 で変更: key パラメータが追加されました。

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

xa にソート順で挿入します。

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 で変更: key パラメータが追加されました。

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 で変更: key パラメータが追加されました。

パフォーマンスに関するメモ

bisect()insort() を使って時間的制約のあるコードを書くときは、以下のことを念頭に置いてください:

  • 二分法は、値の範囲を検索するのに有効です。 特定の値を検索する場合は、辞書の方が効率的です。

  • 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.

  • bisect を利用して、直接の探索ができ、キー関数をサポートする、完全な機能を持つコレクションクラスを組み立てる SortedCollection recipe。キーは、探索中に不必要な呼び出しをさせないために、予め計算しておきます。

ソート済みリストの探索

上記の bisect 関数 は挿入点を見つけるには便利ですが、一般的な検索タスクに使うには厄介であったり、使いにくい場合があります。 以下の5つの関数は、それらをソートされたリストの標準的な検索に変換する方法を示しています:

def index(a, x):
    'xにちょうど等しい左端の値を見つける'
    i = bisect_left(a, x)
    if i != len(a) and a[i] == x:
        return i
    raise ValueError

def find_lt(a, x):
    'xより小さい右端の値を見つける'
    i = bisect_left(a, x)
    if i:
        return a[i-1]
    raise ValueError

def find_le(a, x):
    'x以下の右端の値を見つける'
    i = bisect_right(a, x)
    if i:
        return a[i-1]
    raise ValueError

def find_gt(a, x):
    'xより大きい左端の値を見つける'
    i = bisect_right(a, x)
    if i != len(a):
        return a[i]
    raise ValueError

def find_ge(a, x):
    '左端の項目が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):
...     i = bisect([60, 70, 80, 90], score)
...     return "FDCBA"[i]
...
>>> [grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]
['F', 'A', 'C', 'C', 'B', 'A', 'A']

bisect()insort() 関数もタプルのリストで動作します。 引数 key はテーブルのレコードの順序付けに使用されるフィールドを抽出します:

>>> 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, 'Spielberg'),
...     Movie('Titanic', 1997, 'Cameron'),
...     Movie('The Birds', 1963, 'Hitchcock'),
...     Movie('Aliens', 1986, 'Cameron')
... ]

>>> # 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='Spielberg'),
 Movie(name='Aliens', released=1986, director='Cameron'),
 Movie(name='Titanic', released=1997, director='Cameron')]

キー関数がコストがかかる場合は、レコードのインデックスを見つけるために事前に計算されたキーのリストを検索することにより、関数呼び出しの繰り返しを回避することが可能です:

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