beam: add min_interval_cover greedy with brute-force-verified tests
This commit is contained in:
1 parent
b79f2a6a60
commit
d8d8e3292d
2 files changed
+86
No files matched your search
@@ -0,0 +1,34 @@
|
|||||||
|
"""Minimum number of intervals to cover [0, target).
|
||||||
|
|
||||||
|
Greedy: at each step, among intervals with start <= current coverage end,
|
||||||
|
pick the one extending furthest right. Returns list of chosen intervals
|
||||||
|
or None if full coverage impossible.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
def min_interval_cover(intervals, target):
|
||||||
|
if target <= 0:
|
||||||
|
return []
|
||||||
|
ivs = sorted(intervals, key=lambda x: x[0])
|
||||||
|
chosen = []
|
||||||
|
cur_end = 0
|
||||||
|
i = 0
|
||||||
|
n = len(ivs)
|
||||||
|
while cur_end < target:
|
||||||
|
best_end = cur_end
|
||||||
|
best = None
|
||||||
|
# consider all intervals starting <= cur_end
|
||||||
|
j = 0
|
||||||
|
# find first not considered; use scanning from 0 each iteration is fine for correctness
|
||||||
|
for iv in ivs:
|
||||||
|
if iv[0] <= cur_end and iv[1] > best_end:
|
||||||
|
best_end = iv[1]
|
||||||
|
best = iv
|
||||||
|
if best is None or best_end == cur_end:
|
||||||
|
return None
|
||||||
|
chosen.append(best)
|
||||||
|
cur_end = best_end
|
||||||
|
# remove used interval to avoid infinite loop? If best still best, next iteration best_end would equal cur_end -> None. Remove it.
|
||||||
|
ivs = [x for x in ivs if x is not best]
|
||||||
|
return chosen
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import sys; sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent))
|
||||||
|
from interval_cover import min_interval_cover
|
||||||
|
|
||||||
|
# basic
|
||||||
|
assert min_interval_cover([(0,5),(5,10)], 10) is not None
|
||||||
|
r = min_interval_cover([(0,5),(5,10)], 10)
|
||||||
|
assert len(r)==2
|
||||||
|
# impossible
|
||||||
|
assert min_interval_cover([(0,4),(5,10)], 10) is None
|
||||||
|
# single
|
||||||
|
assert min_interval_cover([(0,10)], 10) == [(0,10)]
|
||||||
|
# empty target
|
||||||
|
assert min_interval_cover([(0,5)], 0) == []
|
||||||
|
# greedy optimal check vs brute for small
|
||||||
|
import itertools
|
||||||
|
def brute(intervals,target):
|
||||||
|
# min subset covering [0,target)
|
||||||
|
best=None
|
||||||
|
for r in range(1,len(intervals)+1):
|
||||||
|
for combo in itertools.combinations(intervals,r):
|
||||||
|
# check coverage
|
||||||
|
cur=0
|
||||||
|
# sort
|
||||||
|
s=sorted(combo)
|
||||||
|
ok=True
|
||||||
|
for a,b in s:
|
||||||
|
if a>cur: ok=False;break
|
||||||
|
cur=max(cur,b)
|
||||||
|
if ok and cur>=target:
|
||||||
|
best=r; break
|
||||||
|
if best: break
|
||||||
|
return best
|
||||||
|
import random
|
||||||
|
random.seed(1)
|
||||||
|
for _ in range(2000):
|
||||||
|
n=random.randint(1,6)
|
||||||
|
ivs=[]
|
||||||
|
for _ in range(n):
|
||||||
|
a=random.randint(0,6); b=a+random.randint(1,4)
|
||||||
|
ivs.append((a,b))
|
||||||
|
t=random.randint(1,8)
|
||||||
|
res=min_interval_cover(ivs,t)
|
||||||
|
got = len(res) if res is not None else None
|
||||||
|
# feasibility
|
||||||
|
feas = any(True for _ in [0])
|
||||||
|
# compute brute min if feasible
|
||||||
|
b=brute(ivs,t)
|
||||||
|
if b is None:
|
||||||
|
assert res is None, (ivs,t,res)
|
||||||
|
else:
|
||||||
|
assert res is not None and len(res)==b, (ivs,t,res,b)
|
||||||
|
print("all ok")
|
||||||
Reference in new issue
Block a user