Sorting, Key Functions, bisect & heapq
Algorithmic performance in Python relies heavily on C-accelerated searching and sorting primitives. Timsort (list.sort(), sorted()), Binary Search (bisect), and Priority Queues (heapq) provide sub-linear and logarithmic operations when handling large datasets.
This chapter details Timsort key-function optimization, binary search insertion via bisect, Min-Heap binary array representations (heapq), and top-K selection performance.
1. Timsort & Key-Function Mechanics (list.sort() / sorted())
Pythonβs default sorting algorithm is Timsortβan adaptive, stable hybrid merge/insertion sort algorithm operating in $O(N \log N)$ worst-case time and $O(N)$ best-case time for pre-sorted arrays.
Key Function Optimization:
When sorting using key=fn (e.g. sorted(users, key=lambda u: u.age)):
- Single Evaluation: The
keyfunction is evaluated exactly once per item (Dschwartzian Transform). - Tuple Comparison: CPython builds a temporary array of
(key_value, original_item)tuples and sorts them in native C memory, avoiding Python function callback overhead during comparisons.
# Sort complex objects by multi-attribute tuple priority
users.sort(key=lambda u: (-u.score, u.name)) # Score descending, Name ascending2. Fast Binary Search with bisect
The bisect module implements binary search on pre-sorted sequences in $O(\log N)$ time:
bisect_left(a, x): Returns the leftmost insertion index forxto maintain sorted order.bisect_right(a, x): Returns the rightmost insertion index forx.insort(a, x): Finds insertion index via binary search and executesa.insert(idx, x).
import bisect
# Grade lookup using boundary thresholds
cutoffs = [60, 70, 80, 90]
grades = ["F", "D", "C", "B", "A"]
def get_grade(score: int) -> str:
idx = bisect.bisect_right(cutoffs, score)
return grades[idx]
print(get_grade(85)) # Returns "B" in O(log N) time!3. Priority Queues with heapq (Min-Heap)
The heapq module implements a Min-Heap algorithm using standard 0-indexed Python lists, where heap[0] is guaranteed to be the smallest item.
Min-Heap Array Binary Tree Mapping:
Array Index: [ 10, 20, 15, 30, 40 ]
Index Map: 0 1 2 3 4
Tree Structure:
[ 10 ] (index 0)
/ \
(index 1) [ 20 ] [ 15 ] (index 2)
/ \
(index 3) [ 30 ] [ 40 ] (index 4)
Parent/Child Index Math:
- Left Child Index = 2 * i + 1
- Right Child Index = 2 * i + 2
- Parent Index = (i - 1) // 2Core Operations:
heapq.heappush(heap, item): Inserts item and restores min-heap invariant in $O(\log N)$ time.heapq.heappop(heap): Pops and returns smallest item (heap[0]) in $O(\log N)$ time.heapq.heapify(list): Converts a list into a min-heap in-place in $O(N)$ linear time.
4. Top-K Selection Performance (nlargest vs nsmallest)
Finding the top $K$ largest elements from an $N$-item list can be achieved via multiple approaches:
- Full Sort (
sorted(arr)[-K:]): $O(N \log N)$ time. Allocates full sorted list. - Min-Heap Selection (
heapq.nlargest(K, arr)): $O(N \log K)$ time. Maintains a heap of size $K$, skipping full array sorting.
Rule of Thumb: Use
heapq.nlargestwhen $K \ll N$ (e.g. top 10 items out of 1,000,000). If $K \approx N$, fullsorted()in C is faster due to lower constant overhead.