はじめに
標準ライブラリbisect
で配列二分法(リストの二分探索)が実装されています。
今回はbisect
の基本的な使い方を説明した後、自前で実装します。
bisectの基本
どこに挿入すれば良いか?
この節は、基本的な使い方を理解している方はスキップしていただいて問題ありません。
bisect
は昇順ソート済みのリストに対してある値を挿入したい時、ソート状態を維持するにはどこに挿入すれば良いかを計算します。
1 | import bisect |
1 | 2 |
bisect
はinsert
との相性が良いです。
1 | A.insert(ins, x) |
1 | [2, 3, 4, 5, 7, 11] |
bisect_left
とbisect_right
の違い
では、x = 5
のように、すでにリスト内に同じ値が存在する場合はどうなるでしょうか。
1 | A = [2, 3, 5, 7, 11] |
1 | 4 |
リスト内に同じ値がある場合、その右側に挿入できるように位置を返します。
挿入点が左側となるような関数bisect_left
も存在します。
1 | print (bisect.bisect(A, x)) |
1 | 4 |
挿入が目的であればどちらを使っても結果は変わりませんね。
bisect_left
とbisect_right
の使い分け
これらを使い分けする意味がある状況もあります。例えば、ある区間に含まれる要素を数えあげるといったシーンです。
以下のリストの中に3以上7以下
の要素はいくつあるでしょうか。
1 | B = [2, 2, 2, 3, 3, 5, 5, 7, 7, 7, 7, 11, 11] |
bisect_left, bisect_right
を適切に組み合わせることで計算できます。
1 | L = 3 |
1 | 8 |
区間内の要素の数え上げを、閉区間・開区間の概念でまとめると以下のようになります
[L, U]
・・・L以上 U以下- bisect_right(U) - bisect_left(L)
[L, U)
・・・L以上 U未満- bisect_left(U) - bisect_left(L)
(L, U]
・・・Lより大きく U以下- bisect_right(U) - bisect_right(L)
(L, U)
・・・Lより大きく 未満- bisect_left(U) - bisect_right(L)
方針
さて、 本題のbisect
の実装に入りましょう!
リスト化できない関数を探索可能な二分探索
を実装し、それを元にbisect
と等価な機能を実装します。
リストを直接二分探索する実装としても良いのですが、上記の方法がより汎用的なアプローチに思えるためです。
流れ
二分探索の実装
二分探索を行う関数meguru_bisect
はこちらの実装学習する天然ニューラルネットを利用させていただきました。記事の中で説明している通り、実装が簡潔でバグが入り難いと思います。bisect_left
およびbisect_right
の実装
実は、リスト化できない関数を探索可能な二分探索
が実装できてしまうと、ここはとても楽です。bisect_left
とbisect_right
の違いは、等号(=
)の有無だけで表現できます。
あるインデックスi
が与えられた時、事前に用意している値x
とリストa
のi
番目の値を比較するだけです。
- テスト
最後に本家bisect
と等しくなるかをunittest
を使ってテストします。
コード
1 | import bisect |
例題
bisect
を用いる良い題材として、AtCoderの問題があります。ご興味がある方は、
ABC077C Snuke Festivalをご覧ください。
記事情報
- 投稿日:2020年4月12日
- 最終更新日:2020年4月29日