Python - Python bisect Module for Efficient Sorted-List Operations
The Python bisect module provides functions for working efficiently with sorted lists. It uses the binary search technique to locate the correct position where an element should be inserted while keeping the list sorted. Unlike a normal search that may examine elements one by one, binary search repeatedly divides the search area into two parts. This makes finding an insertion position much faster, especially when working with large sorted lists.
The bisect module is part of Python's standard library, so it does not require external installation. It is particularly useful when you frequently need to insert values into an already sorted list. The most commonly used functions are bisect_left() and bisect_right(). Both functions return an index rather than directly modifying the list.
1. Importing the bisect Module
Before using the functions, the module can be imported as follows:
import bisect
Suppose we have a sorted list:
numbers = [10, 20, 30, 40, 50]
If we want to determine where 35 should be inserted, we can use:
position = bisect.bisect_left(numbers, 35)
print(position)
Output:
3
Index 3 is the correct position because inserting 35 at that position produces:
[10, 20, 30, 35, 40, 50]
2. bisect_left()
The bisect_left() function finds the leftmost position at which a value can be inserted while maintaining sorted order.
For example:
import bisect
numbers = [10, 20, 30, 30, 40, 50]
position = bisect.bisect_left(numbers, 30)
print(position)
Output:
2
There are two 30 values in the list. bisect_left() returns the position of the first 30, which is index 2.
This behavior is useful when you need to identify the beginning of a group of equal values.
3. bisect_right()
The bisect_right() function, also available as bisect(), finds the rightmost position where a value can be inserted.
Example:
import bisect
numbers = [10, 20, 30, 30, 40, 50]
position = bisect.bisect_right(numbers, 30)
print(position)
Output:
4
The returned position is after the existing 30 values.
The difference can be summarized as:
bisect_left() -> position before existing equal values
bisect_right() -> position after existing equal values
4. Using insort() to Insert Values
The bisect module also provides functions that locate the insertion position and then insert the value automatically.
The insort_left() function can be used as follows:
import bisect
numbers = [10, 20, 30, 40, 50]
bisect.insort_left(numbers, 35)
print(numbers)
Output:
[10, 20, 30, 35, 40, 50]
Instead of separately finding the index and calling insert(), insort_left() performs the operation directly.
Similarly, insort_right() inserts the value after existing equal values:
import bisect
numbers = [10, 20, 30, 30, 40]
bisect.insort_right(numbers, 30)
print(numbers)
Output:
[10, 20, 30, 30, 30, 40]
5. Searching for a Value in a Sorted List
Although bisect is primarily designed to find insertion positions, it can also be used to determine whether a value exists in a sorted list.
For example:
import bisect
numbers = [10, 20, 30, 40, 50]
value = 40
position = bisect.bisect_left(numbers, value)
if position < len(numbers) and numbers[position] == value:
print("Value found")
else:
print("Value not found")
Output:
Value found
The important point is that bisect_left() gives the potential position. We must then check whether the element at that position actually equals the value we are searching for.
6. Working with Duplicate Values
One of the major advantages of the bisect module is its predictable behavior with duplicate values.
Consider:
numbers = [10, 20, 20, 20, 30, 40]
Using:
bisect.bisect_left(numbers, 20)
returns:
1
while:
bisect.bisect_right(numbers, 20)
returns:
4
Therefore, the range from index 1 to index 4 represents the positions occupied by the value 20.
This can be useful for determining how many times a value occurs:
import bisect
numbers = [10, 20, 20, 20, 30, 40]
left = bisect.bisect_left(numbers, 20)
right = bisect.bisect_right(numbers, 20)
count = right - left
print(count)
Output:
3
This approach is efficient because it takes advantage of the fact that the list is already sorted.
7. Using the key Parameter
Modern versions of Python also allow bisect functions to work with a key function. This is useful when the list contains objects or complex values and sorting is based on a particular attribute.
For example:
import bisect
students = [
{"name": "Anu", "marks": 60},
{"name": "Ravi", "marks": 70},
{"name": "Meena", "marks": 80}
]
marks = [student["marks"] for student in students]
position = bisect.bisect_left(marks, 75)
print(position)
Output:
2
A new student with 75 marks should be placed before the student with 80 marks.
The key principle is that the sequence supplied to bisect must already be ordered according to the same comparison rule being used.
8. Important Requirement: The List Must Be Sorted
The bisect module assumes that the input sequence is already sorted.
For example:
numbers = [10, 40, 20, 50, 30]
position = bisect.bisect_left(numbers, 35)
print(position)
The result cannot be relied upon because the list is not sorted.
The correct approach is:
numbers = [10, 20, 30, 40, 50]
position = bisect.bisect_left(numbers, 35)
print(position)
Therefore, before using bisect, make sure the list is maintained in sorted order.
9. Time Complexity
Finding the insertion position using bisect_left() or bisect_right() uses binary search and takes approximately O(log n) comparisons.
However, inserting an element into a Python list using insort() can still take O(n) time because elements may need to be shifted to make room for the new element.
For example:
bisect.insort(numbers, 35)
The search for the correct position is efficient, but the physical insertion into the list can require shifting many elements.
This distinction is important when dealing with very large lists and frequent insertions.
10. Practical Applications
The bisect module is useful in applications that maintain sorted data dynamically. It can be used for maintaining sorted scores, ranking systems, price lists, timestamps, ranges, and ordered records.
For example, a simple ranking system can maintain scores in sorted order:
import bisect
scores = [45, 60, 72, 85, 91]
new_score = 78
bisect.insort(scores, new_score)
print(scores)
Output:
[45, 60, 72, 78, 85, 91]
The list remains sorted without requiring the programmer to manually search for the correct position.
Conclusion
The Python bisect module is a useful standard-library tool for efficiently finding insertion positions in sorted sequences. bisect_left() finds the position before equal elements, while bisect_right() finds the position after them. insort_left() and insort_right() combine position finding with insertion. The module is especially useful when sorted data must be searched or maintained dynamically. However, the list must already be sorted, and although locating a position is efficient with binary search, inserting into a Python list can still require linear-time element shifting.