use std::ops::Range;
#[derive(Debug, Clone)]
pub struct DirtyRanges {
ranges: Vec<Range<usize>>,
}
impl DirtyRanges {
pub fn new() -> Self {
Self { ranges: Vec::new() }
}
pub fn mark_dirty(&mut self, start: usize, end: usize) {
if start >= end {
return; }
let new_range = start..end;
let mut insert_idx = self.ranges.len();
let mut merge_start_idx = None;
let mut merge_end_idx = None;
for (i, range) in self.ranges.iter().enumerate() {
if ranges_overlap_or_adjacent(&new_range, range) {
if merge_start_idx.is_none() {
merge_start_idx = Some(i);
}
merge_end_idx = Some(i);
} else if range.start > end {
if merge_start_idx.is_none() {
insert_idx = i;
}
break;
}
}
match (merge_start_idx, merge_end_idx) {
(Some(start_idx), Some(end_idx)) => {
let merged_start = self.ranges[start_idx].start.min(start);
let merged_end = self.ranges[end_idx].end.max(end);
self.ranges.drain(start_idx..=end_idx);
self.ranges.insert(start_idx, merged_start..merged_end);
}
(None, None) => {
self.ranges.insert(insert_idx, new_range);
}
_ => unreachable!("merge_start_idx and merge_end_idx should both be Some or None"),
}
}
pub fn mark_dirty_batch(&mut self, ranges: impl IntoIterator<Item = Range<usize>>) {
for range in ranges {
self.mark_dirty(range.start, range.end);
}
}
pub fn clear(&mut self) {
self.ranges.clear();
}
pub fn is_empty(&self) -> bool {
self.ranges.is_empty()
}
pub fn len(&self) -> usize {
self.ranges.len()
}
pub fn iter(&self) -> impl Iterator<Item = &Range<usize>> {
self.ranges.iter()
}
pub fn total_dirty_count(&self) -> usize {
self.ranges.iter().map(|r| r.end - r.start).sum()
}
pub fn contains(&self, index: usize) -> bool {
self.ranges
.iter()
.any(|range| index >= range.start && index < range.end)
}
pub fn as_slice(&self) -> &[Range<usize>] {
&self.ranges
}
pub fn merge_overlapping(&mut self) {
if self.ranges.len() <= 1 {
return;
}
let mut merged = Vec::with_capacity(self.ranges.len());
let mut current = self.ranges[0].clone();
for range in &self.ranges[1..] {
if ranges_overlap_or_adjacent(¤t, range) {
current.end = current.end.max(range.end);
} else {
merged.push(current);
current = range.clone();
}
}
merged.push(current);
self.ranges = merged;
}
pub fn stats(&self) -> DirtyRangeStats {
let total_elements = self.total_dirty_count();
let num_ranges = self.ranges.len();
let avg_range_size = if num_ranges > 0 {
total_elements as f32 / num_ranges as f32
} else {
0.0
};
DirtyRangeStats {
num_ranges,
total_elements,
avg_range_size,
}
}
}
impl Default for DirtyRanges {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy)]
pub struct DirtyRangeStats {
pub num_ranges: usize,
pub total_elements: usize,
pub avg_range_size: f32,
}
fn ranges_overlap_or_adjacent(a: &Range<usize>, b: &Range<usize>) -> bool {
(a.start < b.end && b.start < a.end) || a.end == b.start || b.end == a.start
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single_range() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty(10, 20);
assert_eq!(ranges.len(), 1);
assert_eq!(ranges.as_slice()[0], 10..20);
assert_eq!(ranges.total_dirty_count(), 10);
}
#[test]
fn test_non_overlapping_ranges() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty(10, 20);
ranges.mark_dirty(30, 40);
assert_eq!(ranges.len(), 2);
assert_eq!(ranges.as_slice()[0], 10..20);
assert_eq!(ranges.as_slice()[1], 30..40);
}
#[test]
fn test_overlapping_ranges() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty(10, 20);
ranges.mark_dirty(15, 25);
assert_eq!(ranges.len(), 1, "Overlapping ranges should merge");
assert_eq!(ranges.as_slice()[0], 10..25);
}
#[test]
fn test_adjacent_ranges() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty(10, 20);
ranges.mark_dirty(20, 30);
assert_eq!(ranges.len(), 1, "Adjacent ranges should merge");
assert_eq!(ranges.as_slice()[0], 10..30);
}
#[test]
fn test_contained_range() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty(10, 30);
ranges.mark_dirty(15, 20);
assert_eq!(ranges.len(), 1, "Contained range should not split");
assert_eq!(ranges.as_slice()[0], 10..30);
}
#[test]
fn test_containing_range() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty(15, 20);
ranges.mark_dirty(10, 30);
assert_eq!(ranges.len(), 1, "Containing range should absorb smaller");
assert_eq!(ranges.as_slice()[0], 10..30);
}
#[test]
fn test_multiple_merge() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty(10, 20);
ranges.mark_dirty(30, 40);
ranges.mark_dirty(50, 60);
ranges.mark_dirty(15, 55);
assert_eq!(ranges.len(), 1, "Should merge all overlapping ranges");
assert_eq!(ranges.as_slice()[0], 10..60);
}
#[test]
fn test_insertion_order() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty(30, 40);
ranges.mark_dirty(10, 20);
ranges.mark_dirty(50, 60);
assert_eq!(ranges.len(), 3);
assert_eq!(ranges.as_slice()[0], 10..20);
assert_eq!(ranges.as_slice()[1], 30..40);
assert_eq!(ranges.as_slice()[2], 50..60);
}
#[test]
fn test_clear() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty(10, 20);
ranges.mark_dirty(30, 40);
ranges.clear();
assert_eq!(ranges.len(), 0);
assert!(ranges.is_empty());
}
#[test]
fn test_contains() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty(10, 20);
ranges.mark_dirty(30, 40);
assert!(ranges.contains(10));
assert!(ranges.contains(15));
assert!(ranges.contains(19));
assert!(!ranges.contains(20));
assert!(!ranges.contains(25));
assert!(ranges.contains(35));
}
#[test]
fn test_total_dirty_count() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty(10, 20); ranges.mark_dirty(30, 35);
assert_eq!(ranges.total_dirty_count(), 15);
}
#[test]
fn test_invalid_range() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty(20, 10);
assert_eq!(ranges.len(), 0, "Invalid range should be ignored");
}
#[test]
fn test_zero_length_range() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty(10, 10);
assert_eq!(ranges.len(), 0, "Zero-length range should be ignored");
}
#[test]
fn test_batch_marking() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty_batch(vec![10..20, 30..40, 50..60]);
assert_eq!(ranges.len(), 3);
assert_eq!(ranges.total_dirty_count(), 30);
}
#[test]
fn test_stats() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty(10, 20);
ranges.mark_dirty(30, 40);
let stats = ranges.stats();
assert_eq!(stats.num_ranges, 2);
assert_eq!(stats.total_elements, 20);
assert_eq!(stats.avg_range_size, 10.0);
}
#[test]
fn test_ranges_overlap_or_adjacent_fn() {
assert!(ranges_overlap_or_adjacent(&(10..20), &(15..25)));
assert!(ranges_overlap_or_adjacent(&(10..20), &(20..30)));
assert!(ranges_overlap_or_adjacent(&(20..30), &(10..20)));
assert!(!ranges_overlap_or_adjacent(&(10..20), &(21..30)));
assert!(ranges_overlap_or_adjacent(&(10..30), &(15..20)));
}
#[test]
fn test_complex_merge_scenario() {
let mut ranges = DirtyRanges::new();
ranges.mark_dirty(100, 110);
ranges.mark_dirty(50, 60);
ranges.mark_dirty(80, 90);
ranges.mark_dirty(55, 65); ranges.mark_dirty(75, 85); ranges.mark_dirty(65, 75);
assert_eq!(ranges.len(), 2);
assert_eq!(ranges.as_slice()[0], 50..90);
assert_eq!(ranges.as_slice()[1], 100..110);
}
}