Friday, December 5, 2025

All bisect functions in Python

 Here’s a clean, practical tour of all bisect functions in Python – with code and scenario-based examples.

We’ll cover:

  • bisect_left

  • bisect_right (same as bisect)

  • insort_left

  • insort_right (same as insort)

All of these work on sorted lists. If the list is not sorted, results are wrong.


1. Basic idea: using bisect_left and bisect_right

from bisect import bisect_left, bisect_right

scores = [10, 20, 20, 20, 30, 40]  # sorted list

x = 20

pos_left = bisect_left(scores, x)
pos_right = bisect_right(scores, x)

print("scores:", scores)
print("bisect_left(20)  ->", pos_left)   # first position where 20 can go (leftmost)
print("bisect_right(20) ->", pos_right)  # position after the last 20 (rightmost+1)

Output (conceptually):

  • bisect_left(20)1

  • bisect_right(20)4

When to use which?

  • bisect_left(a, x)

    • Think: “insert before existing equals”

    • Good for lower bound, “first ≥ x”

  • bisect_right(a, x) / bisect(a, x)

    • Think: “insert after existing equals”

    • Good for upper bound, “first > x”


2. Scenario 1 – Grade boundaries (using bisect_right)

You have percentage marks, and you want to map them to grades based on boundary cutoffs.

from bisect import bisect_right

# boundaries: marks at which grade changes
boundaries = [40, 60, 75, 90]  # must be sorted
grades = ["F", "C", "B", "A", "A+"]  # one more than boundaries

def get_grade(score):
    idx = bisect_right(boundaries, score)
    return grades[idx]

for s in [35, 40, 59, 60, 74, 75, 89, 90, 99]:
    print(f"{s} -> {get_grade(s)}")

Logic:

  • bisect_right(boundaries, score) returns how many boundaries the score crosses.

  • We use that number as the index in grades.


3. Scenario 2 – Find position to insert (using bisect_left)

You want to place a new value in a sorted list but without actually inserting – just find where it would go.

from bisect import bisect_left

data = [3, 5, 8, 12, 15]

def find_insert_position(sorted_list, value):
    pos = bisect_left(sorted_list, value)
    return pos

for v in [1, 5, 7, 20]:
    pos = find_insert_position(data, v)
    print(f"Value {v} should go at index {pos} in {data}")

Use this when you just need the index, e.g., to split a list, or for binary search style lookup.


4. Inserting while keeping the list sorted – insort_left & insort_right

These are like bisect_... + list.insert(...) combined.

from bisect import insort_left, insort_right

nums = [10, 20, 20, 30]

print("Original:", nums)

# Insert with insort_left (before existing equals)
insort_left(nums, 20)
print("After insort_left(20): ", nums)

# Insert with insort_right (after existing equals)
insort_right(nums, 20)
print("After insort_right(20):", nums)

Behavior:

  • insort_left(a, x)
    Insert x at the position returned by bisect_left(a, x) (before equals)

  • insort_right(a, x) / insort(a, x)
    Insert x at the position returned by bisect_right(a, x) (after equals)


5. Scenario 3 – Maintaining a sorted list (online data)

Imagine you get sensor readings one by one, and you want to keep them in sorted order to quickly get median or percentiles.

from bisect import insort

readings = []

def add_reading(value):
    # insort == insort_right
    insort(readings, value)

def get_median():
    n = len(readings)
    if n == 0:
        return None
    mid = n // 2
    if n % 2 == 1:
        return readings[mid]
    else:
        return (readings[mid - 1] + readings[mid]) / 2

for r in [10, 5, 8, 15, 3, 12]:
    add_reading(r)
    print("After adding", r, "=> readings:", readings, "median:", get_median())

Here:

  • New values are inserted in O(n) time but still using binary search internally for the position.

  • Lookup for median is O(1) once sorted.


6. Scenario 4 – Counting elements in a range

Given a sorted list, how many values fall between low and high?

from bisect import bisect_left, bisect_right

data = [2, 4, 4, 4, 7, 9, 10, 10, 15]  # sorted

def count_in_range(sorted_list, low, high):
    # first index with value >= low
    left = bisect_left(sorted_list, low)
    # first index with value > high
    right = bisect_right(sorted_list, high)
    return right - left

print(count_in_range(data, 4, 10))   # how many values between 4 and 10 inclusive?
print(count_in_range(data, 5, 9))    # values between 5 and 9
print(count_in_range(data, 0, 100))  # all values

Key idea:

  • left = position of first value ≥ low

  • right = position of first value > high

  • count = right - left


7. Scenario 5 – Implementing your own binary search using bisect_left

You can use bisect_left as a helper to implement “does this value exist?” in a sorted list.

from bisect import bisect_left

def contains(sorted_list, value):
    i = bisect_left(sorted_list, value)
    # i is index where value *should* be inserted to stay sorted
    if i != len(sorted_list) and sorted_list[i] == value:
        return True
    return False

arr = [1, 3, 4, 7, 9, 11]

for v in [3, 5, 11, 12]:
    print(f"{v} exists? -> {contains(arr, v)}")

8. Quick summary of all bisect functions

from bisect import (
    bisect_left,
    bisect_right,  # same as bisect
    bisect,        # alias of bisect_right
    insort_left,
    insort_right,  # same as insort
    insort,        # alias of insort_right
)
  • Search / index only

    • bisect_left(a, x) → index of first element ≥ x

    • bisect_right(a, x) / bisect(a, x) → index of first element > x

  • Insert while keeping list sorted

    • insort_left(a, x) → insert at position bisect_left(a, x)

    • insort_right(a, x) / insort(a, x) → insert at position bisect_right(a, x)

All of them assume a is already sorted.



Featured Post

All bisect functions in Python

 Here’s a clean, practical tour of all bisect functions in Python – with code and scenario-based examples. We’ll cover: bisect_left ...

Popular posts