The bisect module provides binary search operations on sorted sequences. O(log n) lookups and insertions into sorted lists.
Finding Insertion Points
import bisect
scores = [60, 70, 80, 90]
# Where would 75 go?
pos = bisect.bisect(scores, 75)
print(pos) # 2 (between 70 and 80)
# bisect_left vs bisect_right for duplicates
nums = [1, 3, 3, 3, 5]
print(bisect.bisect_left(nums, 3)) # 1 (before 3s)
print(bisect.bisect_right(nums, 3)) # 4 (after 3s)
print(bisect.bisect(nums, 3)) # 4 (same as bisect_right)Inserting While Maintaining Order
import bisect
scores = [60, 70, 80, 90]
# Insert 75 in sorted position
bisect.insort(scores, 75)
print(scores) # [60, 70, 75, 80, 90]
# insort_left vs insort_right for duplicates
nums = [1, 3, 3, 3, 5]
bisect.insort_left(nums, 3) # Insert before existing 3s
bisect.insort_right(nums, 3) # Insert after existing 3sBinary Search
bisect doesn't have a direct "find" function, but you can build one:
import bisect
def binary_search(arr, x):
"""Find index of x in sorted array, or -1 if not found."""
i = bisect.bisect_left(arr, x)
if i < len(arr) and arr[i] == x:
return i
return -1
nums = [1, 3, 5, 7, 9]
print(binary_search(nums, 5)) # 2
print(binary_search(nums, 4)) # -1Grade Lookup
Classic example from the docs:
import bisect
def grade(score, breakpoints=[60, 70, 80, 90], grades='FDCBA'):
"""Convert numeric score to letter grade."""
i = bisect.bisect(breakpoints, score)
return grades[i]
print(grade(85)) # B
print(grade(92)) # A
print(grade(55)) # F
print(grade(70)) # CCustom Key Functions (Python 3.10+)
import bisect
# Sort by custom key
data = [('alice', 25), ('bob', 30), ('carol', 20)]
data.sort(key=lambda x: x[1]) # Sort by age
# Find where (name, 27) would go based on age
ages = [x[1] for x in data] # [20, 25, 30]
pos = bisect.bisect(ages, 27)
print(pos) # 2
# Python 3.10+ has key parameter
# pos = bisect.bisect(data, 27, key=lambda x: x[1])Practical Examples
Maintaining a Leaderboard
import bisect
class Leaderboard:
def __init__(self):
self.scores = []
self.names = []
def add_score(self, name: str, score: int):
# Find position (descending order)
pos = bisect.bisect_left([-s for s in self.scores], -score)
self.scores.insert(pos, score)
self.names.insert(pos, name)
def top_n(self, n: int):
return list(zip(self.names[:n], self.scores[:n]))
board = Leaderboard()
board.add_score("Alice", 150)
board.add_score("Bob", 200)
board.add_score("Carol", 175)
print(board.top_n(3))
# [('Bob', 200), ('Carol', 175), ('Alice', 150)]Range Queries
import bisect
def count_in_range(arr, lo, hi):
"""Count elements where lo <= x < hi."""
return bisect.bisect_left(arr, hi) - bisect.bisect_left(arr, lo)
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(count_in_range(nums, 3, 7)) # 4 (3, 4, 5, 6)Time-Based Data
import bisect
from datetime import datetime, timedelta
class TimeSeriesData:
def __init__(self):
self.timestamps = []
self.values = []
def add(self, timestamp: datetime, value):
pos = bisect.bisect_left(self.timestamps, timestamp)
self.timestamps.insert(pos, timestamp)
self.values.insert(pos, value)
def get_range(self, start: datetime, end: datetime):
start_idx = bisect.bisect_left(self.timestamps, start)
end_idx = bisect.bisect_right(self.timestamps, end)
return list(zip(
self.timestamps[start_idx:end_idx],
self.values[start_idx:end_idx]
))Performance
| Operation | List | bisect |
|---|---|---|
| Find position | O(n) | O(log n) |
| Insert sorted | O(n log n) | O(n)* |
| Maintain sorted | O(n log n) per insert | O(n) per insert |
*The insertion itself is O(n) due to list shifting, but finding the position is O(log n).
For many insertions, consider sortedcontainers.SortedList which is O(log n) for both.
Quick Reference
import bisect
# Find insertion point (right of duplicates)
bisect.bisect(a, x)
bisect.bisect_right(a, x)
# Find insertion point (left of duplicates)
bisect.bisect_left(a, x)
# Insert maintaining sort (right of duplicates)
bisect.insort(a, x)
bisect.insort_right(a, x)
# Insert maintaining sort (left of duplicates)
bisect.insort_left(a, x)| Function | Duplicates | Use Case |
|---|---|---|
bisect_left | Insert before | Find first occurrence |
bisect_right | Insert after | Find insertion point |
insort_left | Insert before | Stable sort behavior |
insort_right | Insert after | Default choice |
bisect is simple but powerful. Anytime you need to maintain sorted order or binary search, it's the tool.
React to this post: