#[derive(Debug, Clone)]
pub struct StreamingPolicy {
num_sinks: usize,
budget: usize,
}
impl StreamingPolicy {
pub fn new(num_sinks: usize, budget: usize) -> Self {
assert!(
budget > num_sinks,
"budget ({budget}) must be > num_sinks ({num_sinks})"
);
Self { num_sinks, budget }
}
pub fn num_sinks(&self) -> usize {
self.num_sinks
}
pub fn budget(&self) -> usize {
self.budget
}
pub fn window_size(&self) -> usize {
self.budget - self.num_sinks
}
pub fn needs_eviction(&self, current_seq_len: usize) -> bool {
current_seq_len > self.budget
}
pub fn select_keep(&self, current_seq_len: usize) -> Vec<usize> {
if current_seq_len <= self.budget {
return (0..current_seq_len).collect();
}
let window = self.window_size();
let recent_start = current_seq_len - window;
let mut keep = Vec::with_capacity(self.budget);
let sinks_end = self.num_sinks.min(current_seq_len);
keep.extend(0..sinks_end);
let actual_start = recent_start.max(sinks_end);
keep.extend(actual_start..current_seq_len);
keep
}
pub fn select_evict(&self, current_seq_len: usize) -> Vec<usize> {
if current_seq_len <= self.budget {
return Vec::new();
}
let keep = self.select_keep(current_seq_len);
let keep_set: std::collections::HashSet<usize> = keep.into_iter().collect();
(0..current_seq_len)
.filter(|i| !keep_set.contains(i))
.collect()
}
pub fn position_ids(&self, current_seq_len: usize) -> Vec<usize> {
self.select_keep(current_seq_len)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_streaming_policy() {
let policy = StreamingPolicy::new(4, 64);
assert_eq!(policy.num_sinks(), 4);
assert_eq!(policy.budget(), 64);
assert_eq!(policy.window_size(), 60);
}
#[test]
fn no_eviction_when_within_budget() {
let policy = StreamingPolicy::new(4, 64);
let keep = policy.select_keep(32);
assert_eq!(keep.len(), 32);
assert_eq!(keep, (0..32).collect::<Vec<_>>());
assert!(!policy.needs_eviction(32));
}
#[test]
fn no_eviction_at_exact_budget() {
let policy = StreamingPolicy::new(4, 64);
let keep = policy.select_keep(64);
assert_eq!(keep.len(), 64);
assert!(!policy.needs_eviction(64));
}
#[test]
fn eviction_beyond_budget() {
let policy = StreamingPolicy::new(4, 64);
assert!(policy.needs_eviction(100));
let keep = policy.select_keep(100);
assert_eq!(keep.len(), 64);
assert_eq!(&keep[..4], &[0, 1, 2, 3]);
assert_eq!(keep[4], 40); assert_eq!(*keep.last().unwrap(), 99);
}
#[test]
fn evict_indices() {
let policy = StreamingPolicy::new(2, 6);
let evict = policy.select_evict(10);
assert_eq!(evict, vec![2, 3, 4, 5]);
}
#[test]
fn position_ids_preserve_original() {
let policy = StreamingPolicy::new(2, 6);
let positions = policy.position_ids(10);
assert_eq!(positions, vec![0, 1, 6, 7, 8, 9]);
}
#[test]
fn single_sink_token() {
let policy = StreamingPolicy::new(1, 5);
let keep = policy.select_keep(20);
assert_eq!(keep.len(), 5);
assert_eq!(keep[0], 0); assert_eq!(keep[1], 16); assert_eq!(*keep.last().unwrap(), 19);
}
#[test]
fn overlap_sinks_and_window() {
let policy = StreamingPolicy::new(4, 8);
let keep = policy.select_keep(6);
assert_eq!(keep.len(), 6);
assert_eq!(keep, vec![0, 1, 2, 3, 4, 5]);
}
#[test]
fn just_over_budget() {
let policy = StreamingPolicy::new(2, 6);
let keep = policy.select_keep(7);
assert_eq!(keep.len(), 6);
assert_eq!(&keep[..2], &[0, 1]);
assert_eq!(&keep[2..], &[3, 4, 5, 6]);
let evict = policy.select_evict(7);
assert_eq!(evict, vec![2]);
}
#[test]
#[should_panic(expected = "budget (4) must be > num_sinks (4)")]
fn budget_must_exceed_sinks() {
StreamingPolicy::new(4, 4);
}
#[test]
fn large_sequence() {
let policy = StreamingPolicy::new(4, 2048);
let keep = policy.select_keep(100_000);
assert_eq!(keep.len(), 2048);
assert_eq!(&keep[..4], &[0, 1, 2, 3]);
assert_eq!(keep[4], 97956);
assert_eq!(*keep.last().unwrap(), 99999);
}
#[test]
fn empty_evict_when_under_budget() {
let policy = StreamingPolicy::new(2, 10);
let evict = policy.select_evict(5);
assert!(evict.is_empty());
}
}