pub trait LimitProvider: Sync {
fn lim_at(&self, p: usize) -> usize;
#[inline]
fn boundary_order(
&self,
p_a: usize,
lim_a: usize,
p_b: usize,
lim_b: usize,
) -> std::cmp::Ordering {
let _ = (p_a, p_b);
lim_a.cmp(&lim_b)
}
}
#[derive(Copy, Clone, Debug)]
pub struct PlainText {
pub n: usize,
}
impl PlainText {
#[inline]
pub fn new(n: usize) -> Self {
Self { n }
}
}
impl LimitProvider for PlainText {
#[inline(always)]
fn lim_at(&self, p: usize) -> usize {
self.n - p
}
}
#[derive(Clone, Debug)]
pub struct SegmentedText {
n: usize,
ends: Vec<u64>,
}
impl SegmentedText {
pub fn from_lengths(text_len: usize, lengths: &[usize]) -> Self {
let mut ends = Vec::with_capacity(lengths.len());
let mut cum: u64 = 0;
for &len in lengths {
cum += len as u64;
ends.push(cum);
}
assert_eq!(
cum as usize, text_len,
"SegmentedText::from_lengths: per-segment lengths sum to {cum} but text_len is {text_len}",
);
Self { n: text_len, ends }
}
pub fn from_ends(text_len: usize, ends: Vec<u64>) -> Self {
assert!(
ends.windows(2).all(|w| w[0] < w[1]),
"SegmentedText::from_ends: ends must be strictly increasing",
);
match ends.last() {
Some(&last) => assert_eq!(
last as usize, text_len,
"SegmentedText::from_ends: last end ({last}) != text_len ({text_len})",
),
None => assert_eq!(
text_len, 0,
"SegmentedText::from_ends: empty ends but text_len ({text_len}) != 0",
),
}
Self { n: text_len, ends }
}
#[inline]
pub fn text_len(&self) -> usize {
self.n
}
#[inline]
pub fn n_segments(&self) -> usize {
self.ends.len()
}
#[inline]
pub fn ends(&self) -> &[u64] {
&self.ends
}
}
impl LimitProvider for SegmentedText {
#[inline]
fn lim_at(&self, p: usize) -> usize {
let i = self.ends.partition_point(|&b| b <= p as u64);
if i < self.ends.len() {
self.ends[i] as usize - p
} else {
self.n - p
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_text_lim_at_matches_n_minus_p() {
let lp = PlainText::new(100);
assert_eq!(lp.lim_at(0), 100);
assert_eq!(lp.lim_at(50), 50);
assert_eq!(lp.lim_at(99), 1);
assert_eq!(lp.lim_at(100), 0);
}
#[test]
fn segmented_from_lengths_cumulates_ends() {
let lp = SegmentedText::from_lengths(15, &[3, 5, 7]);
assert_eq!(lp.n_segments(), 3);
assert_eq!(lp.ends(), &[3, 8, 15]);
}
#[test]
#[should_panic(expected = "sum to")]
fn segmented_from_lengths_rejects_undercoverage() {
let _ = SegmentedText::from_lengths(20, &[3, 5, 7]);
}
#[test]
fn segmented_lim_at_caps_at_next_boundary() {
let lp = SegmentedText::from_lengths(15, &[3, 5, 7]);
assert_eq!(lp.lim_at(0), 3);
assert_eq!(lp.lim_at(1), 2);
assert_eq!(lp.lim_at(2), 1);
assert_eq!(lp.lim_at(3), 5);
assert_eq!(lp.lim_at(5), 3);
assert_eq!(lp.lim_at(7), 1);
assert_eq!(lp.lim_at(8), 7);
assert_eq!(lp.lim_at(14), 1);
assert_eq!(lp.lim_at(15), 0);
}
#[test]
fn segmented_handles_single_segment_text() {
let lp = SegmentedText::from_lengths(10, &[10]);
assert_eq!(lp.lim_at(0), 10);
assert_eq!(lp.lim_at(5), 5);
assert_eq!(lp.lim_at(10), 0);
}
#[test]
fn segmented_handles_empty_text() {
let lp = SegmentedText::from_lengths(0, &[]);
assert_eq!(lp.n_segments(), 0);
}
#[test]
fn segmented_from_ends_matches_from_lengths() {
let a = SegmentedText::from_lengths(15, &[3, 5, 7]);
let b = SegmentedText::from_ends(15, vec![3, 8, 15]);
assert_eq!(a.ends(), b.ends());
for p in 0..=15 {
assert_eq!(a.lim_at(p), b.lim_at(p), "p={p}");
}
}
}