Assign values to half-open intervals with bisect boundaries
Assign values to half-open intervals with bisect boundaries
For Python interval buckets, choose bisect_left when a cut point belongs to the bucket below it, and choose bisect_right when it belongs to the bucket above it. With cuts [10, 20], that single choice determines where 10 goes: bisect_left returns 0, while bisect_right returns 1.
The example makes both policies visible with values immediately below, at, and above the first cut. bisect_left produces [0, 0, 1]. It models buckets such as (-∞, 10], (10, 20], and so on: a cut is right-closed in the preceding bucket. bisect_right produces [0, 1, 1], modelling (-∞, 10), [10, 20), and so on: a cut is left-closed in the following bucket.
The failing policy is not a Python exception; it is an undocumented switch from one side to the other. The assertion for 10 shows that the two choices intentionally disagree. Name the policy near the code and test equality at every important cut, especially if bucket numbers later select rates, labels, or rules.
The cuts must already be sorted. These functions locate insertion points using ordering, rather than equality tests, and do not validate the semantic meaning of a bucket. The key parameter for bisect_left and bisect_right requires Python 3.10+; this integer-only example works on older supported Python versions. See the Python bisect documentation.
AI assistance disclosure: this article was drafted with AI assistance and its synthetic example was checked for deterministic output.
from bisect import bisect_left, bisect_right
cuts = [10, 20]
values = [9, 10, 11]
# Cut points stay in the bucket below: (-inf, 10], (10, 20].
right_closed = [bisect_left(cuts, value) for value in values]
# Cut points move to the bucket above: (-inf, 10), [10, 20).
left_closed = [bisect_right(cuts, value) for value in values]
assert right_closed == [0, 0, 1]
assert left_closed == [0, 1, 1]
assert right_closed[1] != left_closed[1]
print("values:", values)
print("cut belongs below:", right_closed)
print("cut belongs above:", left_closed)
values: [9, 10, 11]
cut belongs below: [0, 0, 1]
cut belongs above: [0, 1, 1]