Python bisect_leftとbisect_rightの違い|同じ値があるときの返り値と使い分け早見表
bisect_left()とbisect_right()は、どちらもソート済みリストで「xを入れても並び順が崩れない位置」を返します。違いが出るのはxと同じ値がすでにあるときだけで、bisect_leftは同じ値の一番左(手前)、bisect_rightは同じ値の一番右の次(後ろ)の位置を返します。
この記事では、返り値の違いを図と表で確かめたうえで、存在確認・個数・範囲の取り出し・区分けでどちらを使うかの早見表、key引数、自分で実装する方法、よくある間違いまでを、Python 3.12で実行した結果つきでまとめます(2026年10月18日確認)。bisectモジュール全体の使い方はPython bisectとは?をご覧ください。
bisect_leftとbisect_rightの違い(実行結果)
import bisect
a = [10, 20, 20, 20, 30]
for x in (5, 20, 25, 35):
print(f"x={x}: left={bisect.bisect_left(a, x)} right={bisect.bisect_right(a, x)}")
x=5: left=0 right=0
x=20: left=1 right=4
x=25: left=4 right=4
x=35: left=5 right=5
リストに無い値(5・25・35)では同じ位置になり、リストにある値(20)のときだけ分かれます。
添字: 0 1 2 3 4
値: 10 20 20 20 30
↑ ↑
bisect_left=1 bisect_right=4
公式ドキュメントの定義で言い換えると、次のようになります(bisect — Array bisection algorithm)。
| 関数 | 返す位置 i の条件 | 同じ値があるとき |
|---|---|---|
bisect_left(a, x) |
a[:i] はすべて x より小さく、a[i:] はすべて x 以上 | 同じ値の手前(左) |
bisect_right(a, x) |
a[:i] はすべて x 以下で、a[i:] はすべて x より大きい | 同じ値の後ろ(右) |
bisect(a, x) |
bisect_right と同じ(別名) | 後ろ(右) |
つまり、bisect_leftは「x以上が始まる位置」、bisect_rightは「xより大きいものが始まる位置」です。この言い方で覚えると、次の使い分けがすぐ判断できます。
print(bisect.bisect is bisect.bisect_right, bisect.insort is bisect.insort_right)
True True
短い名前のbisect()とinsort()はright側です。読み違いを防ぐため、コードではbisect_left/bisect_rightと明示するのがおすすめです。
どちらを使うかの早見表
| やりたいこと | 書き方 |
|---|---|
| xがあるか調べる | i = bisect_left(a, x) → i < len(a) and a[i] == x |
| xの個数を数える | bisect_right(a, x) - bisect_left(a, x) |
| x以上 y以下を取り出す | a[bisect_left(a, x):bisect_right(a, y)] |
| xより大きく y未満を取り出す | a[bisect_right(a, x):bisect_left(a, y)] |
| x以下で最大の値 | a[bisect_right(a, x) - 1](0なら該当なし) |
| x未満で最大の値 | a[bisect_left(a, x) - 1](0なら該当なし) |
| x以上で最小の値 | a[bisect_left(a, x)](len(a)なら該当なし) |
| xより大きい最小の値 | a[bisect_right(a, x)](len(a)なら該当なし) |
| x以下の要素数 | bisect_right(a, x) |
| x未満の要素数 | bisect_left(a, x) |
C++に慣れている人向けに言うと、bisect_leftはstd::lower_bound、bisect_rightはstd::upper_boundに当たります(lower_boundは「valueより前に並ばない最初の要素」を探す関数です。cppreference: std::lower_bound)。
使い分けの例
存在確認はbisect_left
def contains(a, x):
i = bisect.bisect_left(a, x)
return i != len(a) and a[i] == x
f = [10, 20, 30]
print(contains(f, 20), contains(f, 25), contains(f, 99))
True False False
bisect_rightで同じことをすると、20があっても「20の次」の位置(30)を見てしまい、Falseになります。i != len(a)のチェックは、xが最大値より大きいときにa[i]でIndexErrorになるのを防ぐためです。
個数は2つの差
import bisect, time
a = sorted([i // 3 for i in range(1_000_000)]) # 各値が3個ずつ
s = time.perf_counter(); c1 = a.count(1000); t1 = time.perf_counter() - s
s = time.perf_counter(); c2 = bisect.bisect_right(a, 1000) - bisect.bisect_left(a, 1000); t2 = time.perf_counter() - s
print(c1, f"count(): {t1*1000:.3f} ms")
print(c2, f"bisect : {t2*1000:.3f} ms")
3 count(): 4.984 ms
3 bisect : 0.005 ms
count()はリスト全体を数えますが、bisectは二分探索2回(それぞれO(log n))で済みます。100万件で約1,000倍の差が出ました(手元の1回の計測です)。
範囲の取り出し
scores = [10, 25, 30, 42, 55, 60, 78, 90]
print(scores[bisect.bisect_left(scores, 30):bisect.bisect_right(scores, 60)]) # 30以上60以下
print(scores[bisect.bisect_right(scores, 30):bisect.bisect_left(scores, 60)]) # 30より大きく60未満
[30, 42, 55, 60]
[42, 55]
「以上・以下」はleft→right、「より大きい・未満」はright→leftの組み合わせです。
境界値をどちらに入れるか(成績の区分)
点数から評価を決めるような区分けでは、境界ちょうどの値をどちらに入れるかでleftとrightが変わります。
breakpoints = [60, 70, 80, 90]
grades = "DCBAS"
for score in (59, 60, 89, 90, 100):
print(score, grades[bisect.bisect_right(breakpoints, score)], grades[bisect.bisect_left(breakpoints, score)])
59 D D
60 C D
89 A A
90 S A
100 S S
「60点以上はC」のように境界を上の区分に入れるならbisect_right、「60点を超えたらC」のように下の区分に残すならbisect_leftです。
key引数(Python 3.10以降)
Python 3.10から、bisect_left/bisect_rightにkey引数が追加されました。公式ドキュメントのとおり、keyはリストの要素にだけ適用され、xには適用されません。
users = [("Sato", 18), ("Suzuki", 20), ("Tanaka", 20), ("Ito", 25)] # 年齢順
print(bisect.bisect_left(users, 20, key=lambda u: u[1]),
bisect.bisect_right(users, 20, key=lambda u: u[1]))
1 3
xにはタプルではなく、比べたい値(20)をそのまま渡します。また、loとhiで探す範囲を絞れます(例:bisect.bisect_left(a, 20, 2)は添字2以降だけを探して2を返します)。
よくある間違い
降順のリストに使う
d = [30, 20, 10]
print(bisect.bisect_left(d, 20)) # 正しくない
print(bisect.bisect_left(d, -20, key=lambda v: -v)) # 符号を反転して昇順として扱う
0
1
bisectは昇順を前提にしているため、降順のままでは意味のない位置が返ります。エラーにならないので気づきにくい点に注意してください。ソートしていないリストでも同様です。
挿入にコストがかかることを忘れる
位置を探すのはO(log n)ですが、insortやlist.insert()で実際に入れる処理は要素をずらすためO(n)かかります(公式ドキュメントにも同じ注意があります)。挿入の詳しい使い方はbisect.insort()で解説しています。
bisect_leftとbisect_rightを自分で実装する
違いは比較の1行だけです。CPythonのLib/bisect.pyと同じ考え方で書くと次のようになります。
def my_bisect_left(a, x):
lo, hi = 0, len(a)
while lo < hi:
mid = (lo + hi) // 2
if a[mid] < x: # xより小さいなら右へ
lo = mid + 1
else:
hi = mid
return lo
def my_bisect_right(a, x):
lo, hi = 0, len(a)
while lo < hi:
mid = (lo + hi) // 2
if x < a[mid]: # xより大きいなら左へ
hi = mid
else:
lo = mid + 1
return lo
a = [10, 20, 20, 20, 30]
print(my_bisect_left(a, 20), my_bisect_right(a, 20))
1 4
xと等しい要素に当たったとき、leftは左側(hi = mid)へ、rightは右側(lo = mid + 1)へ進むので、結果が同じ値の左端と右端に分かれます。ランダムなソート済みリスト1万通りで標準ライブラリと結果が一致することを確認しました。二分探索の考え方は二分探索とは?で詳しく解説しています。
アルゴリズムは、自分でコードを書き換えて結果を見比べると理解が早く進みます。通学型のプログラミングスクール「テックジム」では、自分のペースで課題を進めながら、わからないところをトレーナーに質問できます。東京本校(永田町)では無料Python講座(ゼロからはじめるPython入門講座)も毎週開いています。
よくある質問
Q. bisect_leftとbisect_rightの違いは何ですか?
同じ値がすでにリストにあるとき、bisect_leftはその値の手前(左端)、bisect_rightはその値の後ろ(右端の次)の位置を返します。同じ値が無ければ結果は同じです。
Q. bisect.bisect()はleftとrightのどちらですか?
bisect_rightの別名です。insort()もinsort_rightの別名です。読み違いを防ぐため、bisect_left/bisect_rightと明示して書くのがおすすめです。
Q. 値が存在するか調べるにはどちらを使いますか?
bisect_leftを使い、返った位置iが len(a) 未満で a[i] == x かどうかを確かめます。bisect_rightだと同じ値の次の位置が返るため、正しく判定できません。
Q. bisect_leftとbisect_rightの計算量は?
どちらも二分探索なのでO(log n)です。ただし見つけた位置に挿入する処理(insortやinsert)は要素をずらすためO(n)かかります。
Q. 降順のリストでbisectを使えますか?
そのままでは使えません。Python 3.10以降なら key=lambda v: -v とし、xにも符号を反転した値を渡すことで、昇順と同じように扱えます。





