Day 4: Solution Explanation
Approach
Day 4's problem involves working with ranges and determining relationships between them. We need to check:
- Part 1: Whether one range fully contains the other (subset relationship)
- Part 2: Whether two ranges overlap at all (intersection relationship)
The core of the solution is to extend Rust's RangeInclusive
type with functionality to check for these two conditions.
Implementation Details
Range Extension Trait
The most elegant part of this solution is defining a trait to extend the functionality of Rust's built-in RangeInclusive
type:
#![allow(unused)] fn main() { trait InclusiveRangeExt { fn is_subset(&self, other: &Self) -> bool; fn is_overlapping(&self, other: &Self) -> bool; } }
This trait adds two methods to RangeInclusive
:
is_subset
- Checks if the other range is fully contained within this rangeis_overlapping
- Checks if this range overlaps with the other range at all
Implementing the Trait
The implementation uses the contains
method that's built into RangeInclusive
:
#![allow(unused)] fn main() { impl<T> InclusiveRangeExt for RangeInclusive<T> where T : PartialOrd { fn is_subset(&self, other: &Self) -> bool { self.contains(other.start()) && self.contains(other.end()) } fn is_overlapping(&self, other: &Self) -> bool { self.contains(other.start()) || self.contains(other.end()) } } }
The generic implementation works for any type T
that can be compared (PartialOrd
), which includes the integers we're using in this problem.
Parsing the Input
The input consists of pairs of ranges in the format a-b,c-d
. We parse this into pairs of RangeInclusive<u32>
:
#![allow(unused)] fn main() { let pairs = data.lines() .map(|line| line.split(|c:char| c.is_ascii_punctuation()) .map(|c| u32::from_str(c).unwrap_or_else(|e| panic!("{e}")) ) .collect::<Vec<_>>() ) .map(|pair| { let [a, b, c, d] = pair[..] else { panic!("") }; ((a..=b), (c..=d)) }) .collect::<Vec<_>>(); }
The parsing works by:
- Splitting each line by punctuation characters (hyphens and commas)
- Converting each part to a
u32
- Creating a pair of ranges using the inclusive range syntax
a..=b
Solving Part 1: Full Containment
With our ranges parsed and the extension trait implemented, solving Part 1 is straightforward:
#![allow(unused)] fn main() { let out = pairs.iter() .filter(|(a,b)| a.is_subset(b) || b.is_subset(a) ) .count(); }
We check each pair to see if either range is a subset of the other, and count the number of pairs that satisfy this condition.
Solving Part 2: Overlapping
Similarly, for Part 2, we count pairs where ranges overlap at all:
#![allow(unused)] fn main() { let out = pairs.iter() .filter(|(a,b)| a.is_overlapping(b) || b.is_overlapping(a) ) .count(); }
Alternative Solutions
Direct Range Comparison
Instead of using a trait extension, we could have compared range endpoints directly:
#![allow(unused)] fn main() { // Check if range a fully contains range b fn is_subset(a: &(u32, u32), b: &(u32, u32)) -> bool { a.0 <= b.0 && a.1 >= b.1 } // Check if ranges a and b overlap fn is_overlapping(a: &(u32, u32), b: &(u32, u32)) -> bool { a.0 <= b.1 && a.1 >= b.0 } }
This approach would use tuples instead of ranges, which is simpler but less expressive.
Using Set Operations
Another approach could model ranges as sets and use set operations:
#![allow(unused)] fn main() { use std::collections::HashSet; fn range_to_set(start: u32, end: u32) -> HashSet<u32> { (start..=end).collect() } fn is_subset(a: &HashSet<u32>, b: &HashSet<u32>) -> bool { a.is_subset(b) || b.is_subset(a) } fn is_overlapping(a: &HashSet<u32>, b: &HashSet<u32>) -> bool { !a.is_disjoint(b) } }
However, this would be less efficient for large ranges due to the memory required to store every integer in each range.
Time and Space Complexity
- Time Complexity: O(n) where n is the number of range pairs, since we process each pair once with constant-time operations.
- Space Complexity: O(n) to store the parsed pairs.
Conclusion
This solution demonstrates how Rust's trait system can be used to extend existing types with new functionality. By using trait extensions, we achieve an elegant and readable solution that clearly expresses the problem's domain concepts.