beam: add bisect_bounds (lower/upper bound) with randomized tests
This commit is contained in:
1 parent
ab7767d7f0
commit
1a8e9968e7
2 files changed
+92
No files matched your search
@@ -0,0 +1,39 @@
|
||||
"""Lower/upper bound binary search on a sorted sequence.
|
||||
|
||||
Public API:
|
||||
lower_bound(seq, x, *, key=None) -> int
|
||||
upper_bound(seq, x, *, key=None) -> int
|
||||
|
||||
lower_bound returns the first index i such that seq[i] >= x (or len(seq) if none).
|
||||
upper_bound returns the first index i such that seq[i] > x (or len(seq) if none).
|
||||
|
||||
`seq` must be sorted non-decreasingly according to `key` (identity by default).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
from typing import Any, Callable, Sequence
|
||||
|
||||
|
||||
def lower_bound(seq: Sequence[Any], x: Any, *, key: Callable[[Any], Any] | None = None) -> int:
|
||||
k = (lambda v: v) if key is None else key
|
||||
target = x
|
||||
lo, hi = 0, len(seq)
|
||||
while lo < hi:
|
||||
mid = (lo + hi) // 2
|
||||
if k(seq[mid]) < target:
|
||||
lo = mid + 1
|
||||
else:
|
||||
hi = mid
|
||||
return lo
|
||||
|
||||
|
||||
def upper_bound(seq: Sequence[Any], x: Any, *, key: Callable[[Any], Any] | None = None) -> int:
|
||||
k = (lambda v: v) if key is None else key
|
||||
target = x
|
||||
lo, hi = 0, len(seq)
|
||||
while lo < hi:
|
||||
mid = (lo + hi) // 2
|
||||
if k(seq[mid]) <= target:
|
||||
lo = mid + 1
|
||||
else:
|
||||
hi = mid
|
||||
return lo
|
||||
@@ -0,0 +1,53 @@
|
||||
import os, sys, random
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from bisect_bounds import lower_bound, upper_bound
|
||||
|
||||
ok = True
|
||||
def check(name, cond):
|
||||
global ok
|
||||
print(("PASS" if cond else "FAIL") + " " + name)
|
||||
ok = ok and cond
|
||||
|
||||
# empty
|
||||
s = []
|
||||
check("empty lower", lower_bound(s, 5) == 0)
|
||||
check("empty upper", upper_bound(s, 5) == 0)
|
||||
|
||||
# basic
|
||||
s = [1,2,3,4,5]
|
||||
check("lb 3", lower_bound(s,3)==2)
|
||||
check("ub 3", upper_bound(s,3)==3)
|
||||
check("lb 0", lower_bound(s,0)==0)
|
||||
check("ub 9", upper_bound(s,9)==5)
|
||||
check("lb 6", lower_bound(s,6)==5)
|
||||
|
||||
# duplicates
|
||||
s = [2,2,2,3,3]
|
||||
check("lb dup 2", lower_bound(s,2)==0)
|
||||
check("ub dup 2", upper_bound(s,2)==3)
|
||||
check("count dup 2", upper_bound(s,2)-lower_bound(s,2)==3)
|
||||
|
||||
# key
|
||||
items = [("a",1),("b",2),("c",2),("d",3)]
|
||||
check("key lower", lower_bound(items, 2, key=lambda t: t[1])==1)
|
||||
check("key upper", upper_bound(items, 2, key=lambda t: t[1])==3)
|
||||
|
||||
# property randomized vs brute force
|
||||
random.seed(42)
|
||||
for _ in range(2000):
|
||||
n = random.randint(0,30)
|
||||
s = sorted(random.randint(0,9) for _ in range(n))
|
||||
x = random.randint(0,9)
|
||||
lb = lower_bound(s,x)
|
||||
ub = upper_bound(s,x)
|
||||
# verify
|
||||
exp_lb = next((i for i,v in enumerate(s) if v >= x), len(s))
|
||||
exp_ub = next((i for i,v in enumerate(s) if v > x), len(s))
|
||||
if lb != exp_lb or ub != exp_ub:
|
||||
print("MISMATCH", s, x, lb, ub, exp_lb, exp_ub)
|
||||
ok = False
|
||||
break
|
||||
check("randomized", ok)
|
||||
|
||||
print("ALL" if ok else "SOME FAILED")
|
||||
sys.exit(0 if ok else 1)
|
||||
Reference in new issue
Block a user