15. Adaptive Quality Selection Under a Rolling Bandwidth Budget
Problem
A streaming audio client picks, at each measurement interval, the highest quality tier it can sustain without exceeding currently available bandwidth — the same kind of tradeoff a real-time TTS client would need to make between output quality and staying ahead of playback.
Given a sorted list of quality tier bitrates and a stream of bandwidth samples, return, for each sample, the index of the highest tier whose bitrate fits within that sample — or -1 if even the smallest tier doesn't fit.
Source: src/15_adaptive_bitrate_selector.py
def select_quality_tiers(bandwidth_kbps: list[int], tier_bitrates: list[int]) -> list[int]:
>>> select_quality_tiers([50, 120, 30, 200], [32, 64, 128, 256])
[0, 1, -1, 2]
>>> select_quality_tiers([256, 256], [32, 64, 128, 256])
[3, 3]
Step-by-Step Approach
- Notice
tier_bitratesis already sorted ascending — that's the cue to binary search instead of linear-scanning the tier list for every bandwidth sample. - For each bandwidth sample
bw, you want the index of the rightmost tier whose bitrate is<= bw. Python'sbisect.bisect_right(tier_bitrates, bw)gives the insertion point just past every tier<= bw. - Subtract 1 from that insertion point to land on the actual index of the highest affordable tier.
- This subtraction also handles the "nothing fits" case for free: if
bwis smaller than every tier,bisect_rightreturns 0, and0 - 1 == -1is exactly the sentinel the problem wants — no separate branch needed. - Repeat per sample and collect the results; each lookup is O(log t) where t is the number of tiers, so the whole pass is O(n log t) instead of O(n · t) for a linear scan per sample.
The key insight is that "pick the largest sorted value <= x" is exactly what bisect_right(...) - 1 computes, and that the same expression's edge behavior (returning -1 when nothing qualifies) happens to line up with the problem's own sentinel value, eliminating what would otherwise be a separate bounds check.
Reference solution
import bisect
def select_quality_tiers(bandwidth_kbps: list[int], tier_bitrates: list[int]) -> list[int]:
result = []
for bw in bandwidth_kbps:
# bisect_right gives the insertion point just past any tiers <= bw;
# subtracting 1 lands on the highest affordable tier's index, or -1
# naturally if even the smallest tier exceeds bw. O(log t) per sample.
idx = bisect.bisect_right(tier_bitrates, bw) - 1
result.append(idx)
return result
Key Functions & Tricks
bisect.bisect_right(a, x)— returns the insertion point after any existing entries equal to x, i.e. how many elements of the sorted list are <= x.bisect_right(...) - 1— converts that count directly into the index of the largest element <= x, with -1 as the natural "none qualify" result.sorted tier_bitrates precondition— is what makes binary search valid at all — the pattern breaks silently on unsorted input.
How to Recognize This Pattern
Reach for binary search over a sorted reference list whenever a problem asks you to repeatedly find "the largest/smallest value in a sorted list that satisfies some threshold comparison" for a stream of query values — the giveaway is a sorted list plus many independent lookups against it. Python's bisect module (bisect_left/bisect_right) covers most of these directly rather than requiring you to hand-roll the binary search loop. A common variation asks for the smallest value >= x instead (use bisect_left directly, no subtraction needed), or for insertion position rather than a nearest-match index. A common pitfall is confusing bisect_left and bisect_right when the query value exactly equals a list entry — here, an exact match (e.g. bandwidth exactly 256 with a 256 tier) needs bisect_right so that the matching tier itself is included, not skipped.