1use crate::ProgressEntry;
4
5pub trait Merge {
9 fn merge_progress(&mut self, new: ProgressEntry);
18}
19
20impl Merge for Vec<ProgressEntry> {
21 fn merge_progress(&mut self, new: ProgressEntry) {
22 debug_assert!(
23 self.windows(2).all(|w| w[0].end <= w[1].start),
24 "merge_progress requires a sorted, non-overlapping list; merge the entries first"
25 );
26 if new.start >= new.end {
27 return;
28 }
29 let i = self.partition_point(|x| x.end < new.start);
30 if i == self.len() {
31 self.push(new);
32 return;
33 }
34 if self[i].start <= new.start && self[i].end >= new.end {
35 return;
36 }
37 let mut current_merge = new;
38 let mut j = i;
39 while j < self.len() {
40 let entry = &self[j];
41 if entry.start > current_merge.end {
42 break;
43 }
44 current_merge.start = current_merge.start.min(entry.start);
45 current_merge.end = current_merge.end.max(entry.end);
46 j += 1;
47 }
48 if j > i {
49 self.drain(i..j);
50 }
51 self.insert(i, current_merge);
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn test_merge() {
61 #![allow(clippy::single_range_in_vec_init)]
62 let mut v = vec![1..5, 8..10];
63 v.merge_progress(5..10);
64 assert_eq!(v, vec![1..10]);
65 v.merge_progress(10..20);
66 assert_eq!(v, vec![1..20]);
67 v.merge_progress(30..40);
68 assert_eq!(v, vec![1..20, 30..40]);
69 v.merge_progress(21..40);
70 assert_eq!(v, vec![1..20, 21..40]);
71 v.merge_progress(19..21);
72 assert_eq!(v, vec![1..40]);
73 v.merge_progress(50..60);
74 assert_eq!(v, vec![1..40, 50..60]);
75 v.merge_progress(50..60);
76 assert_eq!(v, vec![1..40, 50..60]);
77 v.merge_progress(52..60);
78 assert_eq!(v, vec![1..40, 50..60]);
79 v.merge_progress(52..53);
80 assert_eq!(v, vec![1..40, 50..60]);
81 v.merge_progress(52..61);
82 assert_eq!(v, vec![1..40, 50..61]);
83 v.merge_progress(62..70);
84 assert_eq!(v, vec![1..40, 50..61, 62..70]);
85 v.merge_progress(40..62);
86 assert_eq!(v, vec![1..70]);
87 v.merge_progress(72..82);
88 assert_eq!(v, vec![1..70, 72..82]);
89 v.merge_progress(0..90);
90 assert_eq!(v, vec![0..90]);
91 }
92
93 #[test]
94 fn test_merge_empty_range_is_dropped() {
95 let mut v = vec![1..5, 10..20];
97 v.merge_progress(3..3);
98 assert_eq!(v, vec![1..5, 10..20]);
99
100 v.merge_progress(7..7);
103 assert_eq!(v, vec![1..5, 10..20]);
104 v.merge_progress(25..25);
105 assert_eq!(v, vec![1..5, 10..20]);
106 }
107
108 #[test]
109 fn test_merge_before_front_with_gap() {
110 #![allow(clippy::single_range_in_vec_init)]
111 let mut v = vec![5..10];
113 v.merge_progress(1..3);
114 assert_eq!(v, vec![1..3, 5..10]);
115 }
116
117 #[test]
118 fn test_merge_extends_before_first_and_spans_gaps() {
119 #![allow(clippy::single_range_in_vec_init)]
120 let mut v = vec![1..5, 8..10, 12..15];
123 v.merge_progress(0..13);
124 assert_eq!(v, vec![0..15]);
125 }
126
127 #[test]
128 fn test_merge_reversed_range_is_dropped() {
129 #![allow(clippy::single_range_in_vec_init)]
130 let mut v = vec![10..20];
132 #[allow(clippy::reversed_empty_ranges)]
133 v.merge_progress(30..5);
134 assert_eq!(v, vec![10..20]);
135 }
136
137 #[test]
138 fn test_merge_into_empty_vec() {
139 #![allow(clippy::single_range_in_vec_init)]
140 let mut v: Vec<ProgressEntry> = vec![];
141 v.merge_progress(5..10);
142 assert_eq!(v, vec![5..10]);
143 }
144
145 #[test]
146 fn test_merge_superset_absorbs_all() {
147 #![allow(clippy::single_range_in_vec_init)]
148 let mut v = vec![1..5, 8..10, 20..30];
149 v.merge_progress(0..40);
150 assert_eq!(v, vec![0..40]);
151 }
152
153 #[test]
154 fn test_merge_exact_duplicate_is_noop() {
155 #![allow(clippy::single_range_in_vec_init)]
156 let mut v = vec![1..5];
157 v.merge_progress(1..5);
158 assert_eq!(v, vec![1..5]);
159 }
160
161 #[test]
162 fn test_merge_touching_right_extends() {
163 #![allow(clippy::single_range_in_vec_init)]
164 let mut v = vec![1..5];
165 v.merge_progress(5..8);
166 assert_eq!(v, vec![1..8]);
167 }
168
169 #[test]
170 fn test_merge_touching_left_extends() {
171 #![allow(clippy::single_range_in_vec_init)]
172 let mut v = vec![5..10];
173 v.merge_progress(1..5);
174 assert_eq!(v, vec![1..10]);
175 }
176
177 #[test]
183 fn test_merge_out_of_order_adjacent_coalesces_to_single() {
184 let mut v: Vec<ProgressEntry> = vec![];
185 v.merge_progress(0..50);
187 v.merge_progress(150..200);
188 v.merge_progress(100..150);
189 v.merge_progress(50..100);
190 assert_eq!(v, vec![0..200]);
191
192 let mut w: Vec<ProgressEntry> = vec![];
195 for r in [0..70, 140..200, 70..140] {
196 w.merge_progress(r);
197 }
198 assert_eq!(w, vec![0..200]);
199 assert!(w.len() == 1 && w[0] == (0..200));
200 }
201
202 fn reference_merge(ranges: &[ProgressEntry]) -> Vec<ProgressEntry> {
208 let mut sorted: Vec<ProgressEntry> =
209 ranges.iter().filter(|r| r.start < r.end).cloned().collect();
210 if sorted.is_empty() {
211 return vec![];
212 }
213 sorted.sort_by_key(|r| r.start);
214 let mut merged: Vec<ProgressEntry> = vec![sorted[0].clone()];
215 for r in sorted.iter().skip(1) {
216 let last = merged.last_mut().unwrap();
217 if r.start <= last.end {
218 last.end = last.end.max(r.end); } else {
220 merged.push(r.clone());
221 }
222 }
223 merged
224 }
225
226 #[test]
227 fn merge_agrees_with_reference_oracle() {
228 let cases: &[&[ProgressEntry]] = &[
231 &[1..5, 8..10, 3..12],
232 &[0..10, 20..30, 5..25],
233 &[10..20, 0..5, 4..6, 19..21],
234 &[0..50, 40..60, 55..70, 10..45],
235 &[100..200, 0..50, 50..100],
236 &[0..10, 30..40, 10..30], ];
238 for ranges in cases {
239 let mut v: Vec<ProgressEntry> = vec![];
240 for r in *ranges {
241 v.merge_progress(r.clone());
242 }
243 assert_eq!(v, reference_merge(ranges), "case {ranges:?}");
244 }
245 }
246
247 #[test]
248 fn merge_full_file_coalesces_to_single_regardless_of_order() {
249 let chunks: Vec<ProgressEntry> = (0..50).map(|i| (i * 4)..(i * 4 + 4)).collect();
253 let mut order: Vec<usize> = (0..50).collect();
254 order.sort_by_key(|&i| (i % 7, i)); let mut v: Vec<ProgressEntry> = vec![];
256 for &i in &order {
257 v.merge_progress(chunks[i].clone());
258 }
259 assert_eq!(v, vec![0..200]);
260 }
261
262 #[test]
263 fn merge_new_fills_gap_and_coalesces_all_three() {
264 #![allow(clippy::single_range_in_vec_init)]
265 let mut v = vec![0..10, 30..40];
268 v.merge_progress(10..30);
269 assert_eq!(v, vec![0..40]);
270 }
271
272 #[test]
273 fn merge_accepts_touching_entries() {
274 #![allow(clippy::single_range_in_vec_init)]
275 let mut v = vec![0..10, 10..20];
279 v.merge_progress(5..15);
280 assert_eq!(v, vec![0..20]);
281 }
282}