PythonMastery

Look things up in a sorted list with bisect

A chain of if/elif, or a scan through a sorted list, can be one call to bisect. It halves the search each step and keeps the list in order as you insert.

When the boundaries are sorted, you don't need to walk through them. bisect finds where a value would slot in by halving the list each step, so 1,000,000 boundaries take about 20 steps. It also reads better than a ladder of if statements.

Before

python
def grade(score):
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    elif score >= 60:
        return "D"
    return "F"

print([grade(s) for s in [95, 83, 70, 61, 42]])
output
['A', 'B', 'C', 'D', 'F']

Fine for five grades. Change a boundary or add a band and you are editing code, not data.

After

The boundaries become data. bisect_right returns how many boundaries the score has reached, and that number picks the grade.

python
from bisect import bisect_right

BOUNDARIES = [60, 70, 80, 90]
GRADES = "FDCBA"

def grade(score):
    return GRADES[bisect_right(BOUNDARIES, score)]

print([grade(s) for s in [95, 83, 70, 61, 42]])
output
['A', 'B', 'C', 'D', 'F']

bisect_right puts a score that sits exactly on a boundary (70) above it, into band C. bisect_left would put it below. Pick the one that matches your rule.

Keeping a list sorted as it grows

insort puts a new value in the right place, so the list never needs sorting again:

python
from bisect import insort, bisect_left

prices = [4.99, 9.99, 14.99, 24.99]
insort(prices, 12.49)
print(prices)

budget = 13.00
affordable = prices[:bisect_left(prices, budget)]
print(affordable)
output
[4.99, 9.99, 12.49, 14.99, 24.99]
[4.99, 9.99, 12.49]

Why it works

Each comparison rules out half of what is left, so the number of steps grows with the logarithm of the length, not the length. A scan through a million items might check them all; bisect checks about 20.

When not to use it

The list must already be sorted: on unsorted data bisect quietly returns nonsense rather than failing. insort still has to shift items along to make room, so for thousands of inserts into a huge list a different structure fits better. And if you're only asking "is this value present?", a set is simpler.

Learn it properly: Lists & Sequences, Performance: Profile, Then Optimise