use std::fmt::{Display, Formatter}; // We could use std::ops::RangeInclusive, but we would have to extend it to // normalize self (not much trouble) and it would not have to handle pretty // printing for it explicitly. So, let's make rather an own type. #[derive(Clone, Debug, PartialEq, PartialOrd)] pub struct ClosedRange { start: Idx, end: Idx, } impl ClosedRange { pub fn start(&self) -> &Idx { &self.start } pub fn end(&self) -> &Idx { &self.end } } impl ClosedRange { pub fn new(start: Idx, end: Idx) -> Self { if start <= end { Self { start, end } } else { Self { end: start, start: end, } } } } // To make test input more compact impl From<(Idx, Idx)> for ClosedRange { fn from((start, end): (Idx, Idx)) -> Self { Self::new(start, end) } } // For the required print format impl Display for ClosedRange { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "[{}, {}]", self.start, self.end) } } fn consolidate(a: &ClosedRange, b: &ClosedRange) -> Option> where Idx: PartialOrd + Clone, { if a.start() <= b.start() { if b.end() <= a.end() { Some(a.clone()) } else if a.end() < b.start() { None } else { Some(ClosedRange::new(a.start().clone(), b.end().clone())) } } else { consolidate(b, a) } } fn consolidate_all(mut ranges: Vec>) -> Vec> where Idx: PartialOrd + Clone, { // Panics for incomparable elements! So no NaN for floats, for instance. ranges.sort_by(|a, b| a.partial_cmp(b).unwrap()); let mut ranges = ranges.into_iter(); let mut result = Vec::new(); if let Some(current) = ranges.next() { let leftover = ranges.fold(current, |mut acc, next| { match consolidate(&acc, &next) { Some(merger) => { acc = merger; } None => { result.push(acc); acc = next; } } acc }); result.push(leftover); } result } #[cfg(test)] mod tests { use super::{consolidate_all, ClosedRange}; use std::fmt::{Display, Formatter}; struct IteratorToDisplay(F); impl Display for IteratorToDisplay where F: Fn() -> I, I: Iterator, I::Item: Display, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let mut items = self.0(); if let Some(item) = items.next() { write!(f, "{}", item)?; for item in items { write!(f, ", {}", item)?; } } Ok(()) } } macro_rules! parameterized { ($($name:ident: $value:expr,)*) => { $( #[test] fn $name() { let (input, expected) = $value; let expected: Vec<_> = expected.into_iter().map(ClosedRange::from).collect(); let output = consolidate_all(input.into_iter().map(ClosedRange::from).collect()); println!("{}: {}", stringify!($name), IteratorToDisplay(|| output.iter())); assert_eq!(expected, output); } )* } } parameterized! { single: (vec![(1.1, 2.2)], vec![(1.1, 2.2)]), touching: (vec![(6.1, 7.2), (7.2, 8.3)], vec![(6.1, 8.3)]), disjoint: (vec![(4, 3), (2, 1)], vec![(1, 2), (3, 4)]), overlap: (vec![(4.0, 3.0), (2.0, 1.0), (-1.0, -2.0), (3.9, 10.0)], vec![(-2.0, -1.0), (1.0, 2.0), (3.0, 10.0)]), integer: (vec![(1, 3), (-6, -1), (-4, -5), (8, 2), (-6, -6)], vec![(-6, -1), (1, 8)]), } } fn main() { // To prevent dead code and to check empty input consolidate_all(Vec::>::new()); println!("Run: cargo test -- --nocapture"); }