9.3 二分法アルゴリズムを利用する — bisect
Pythonの bisect モジュールは、すでにソートされている(並び替えられている)リストに対して、順序を保ったまま新しい要素を挿入したり、挿入すべき位置を検索したりするためのモジュールです。
内部で二分探索(バイナリサーチ)アルゴリズムを使用しているため、リストの要素数がどれだけ増えても、非常に高速に処理を行えるのが最大の特徴です。
本記事では、「9.3 二分法アルゴリズムを利用する」として、bisect モジュールの基本と実践的な使い方を解説します。
1. なぜ bisect が必要なのか?(二分探索の力)
Section titled “1. なぜ bisect が必要なのか?(二分探索の力)”通常、リストの中に新しい要素を正しい順序で挿入しようとすると、先頭から順番に大きさを比較していく必要があります(線形探索)。この方法だと、データが100万件あれば最悪100万回の比較が必要です。
しかし、データが「すでにソートされている」という前提があれば、二分探索が使えます。真ん中のデータと比較し、探している値がそれより小さければ左半分、大きければ右半分……と探索範囲を半分ずつに絞り込んでいく手法です。
これを使えば、100万件のデータでもわずか20回程度の比較で目的の位置を見つけることができ、処理速度が劇的に向上します。
2. 挿入箇所のインデックスを取得する:bisect()
Section titled “2. 挿入箇所のインデックスを取得する:bisect()”要素をリストに追加する前に、「もし追加するとしたら、どのインデックス(位置)になるか」を調べる関数です。リスト自体は書き換えません。
bisect.bisect(a, x): リストaの中で、値xが入るべきインデックスを返します。bisect.bisect_right(a, x):bisect()と全く同じ働きをします。もし同じ値が存在した場合は、**その値の右側(後ろ)**のインデックスを返します。bisect.bisect_left(a, x): もし同じ値が存在した場合は、**その値の左側(前)**のインデックスを返します。
import bisect
# 必ずソート済みのリストを用意するdata = [10, 20, 20, 20, 30, 40]
# 25 が入るべき位置(インデックス)を探すindex = bisect.bisect(data, 25)print(index) # 出力: 4
# 同じ値(20)が存在する場合の違いprint(bisect.bisect_right(data, 20)) # 出力: 4 (20のグループの右端)print(bisect.bisect_left(data, 20)) # 出力: 1 (20のグループの左端)3. リストに要素を挿入する:insort()
Section titled “3. リストに要素を挿入する:insort()”インデックスを調べるだけでなく、実際にリストへ要素を挿入する関数です。内部的には bisect で位置を探し、リストの insert() メソッドを呼び出しています。
bisect.insort(a, x): リストaに値xを挿入します。bisect.insort_right(a, x):insort()と同じ働きです(同じ値があれば右側に挿入)。bisect.insort_left(a, x): 同じ値があれば左側に挿入します。
import bisect
numbers = [10, 20, 30, 40]
# 25を適切な位置に挿入bisect.insort(numbers, 25)print(numbers)# 出力: [10, 20, 25, 30, 40] (ソート順が保たれている)4. 実用的な活用テクニック:点数によるランク付け
Section titled “4. 実用的な活用テクニック:点数によるランク付け”bisect モジュールの公式ドキュメントでも紹介されている、非常に実用的なテクニックです。
ある点数がどのランク(A, B, C, Dなど)に該当するかを、大量の if-elif 文を書かずに判定できます。
import bisect
def get_grade(score): # 区切りの境界値(ソート済みであること) breakpoints = [60, 70, 80, 90] # 対応するランク(インデックスと連動させる) grades = ['F', 'D', 'C', 'B', 'A']
# score が breakpoints のどの位置に入るかを取得し、 # そのインデックスを使って grades からランクを取り出す index = bisect.bisect(breakpoints, score) return grades[index]
# テストscores = [33, 99, 77, 70, 89, 90, 100]results = [get_grade(score) for score in scores]
print(results)# 出力: ['F', 'A', 'C', 'C', 'B', 'A', 'A']このように、境界値のリストに対して bisect() を実行するだけで、複雑な条件分岐をシンプルかつ高速に処理することができます。