Day 4: Code
Below is the complete code for Day 4's solution, which handles range containment and overlap checks.
Full Solution
use std::ops::RangeInclusive;
use std::str::FromStr;
trait InclusiveRangeExt {
fn is_subset(&self, other: &Self) -> bool;
fn is_overlapping(&self, other: &Self) -> bool;
}
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())
}
}
fn main() {
let data = std::fs::read_to_string("src/bin/day4_input.txt").expect("Ops! Cannot read file");
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<_>>();
let out = pairs.iter()
.filter(|(a,b)|
a.is_subset(b) || b.is_subset(a)
)
.count();
println!("Component 1 = {out}");
let out = pairs.iter()
.filter(|(a,b)|
a.is_overlapping(b) || b.is_overlapping(a)
)
.count();
println!("Component 2 = {out}");
}
Code Walkthrough
Extending Ranges with a Trait
trait InclusiveRangeExt {
fn is_subset(&self, other: &Self) -> bool;
fn is_overlapping(&self, other: &Self) -> bool;
}
The solution defines a trait to extend Rust's RangeInclusive
type with two new methods for checking containment relationships:
is_subset
- Checks if one range is fully contained within anotheris_overlapping
- Checks if two ranges overlap at all
Implementing the Trait
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 trait is implemented generically for any RangeInclusive<T>
where T
supports partial ordering. This allows the solution to work with ranges of any comparable type, not just integers.
Parsing Input
let data = std::fs::read_to_string("src/bin/day4_input.txt").expect("Ops! Cannot read file");
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 involves several steps:
- Read the input file as a string
- Split each line into parts using punctuation characters (hyphens and commas)
- Convert each part to a
u32
number - Group the numbers into pairs of ranges using Rust's inclusive range syntax
a..=b
Part 1: Checking Subset Relationships
let out = pairs.iter()
.filter(|(a,b)|
a.is_subset(b) || b.is_subset(a)
)
.count();
This part counts pairs where one range fully contains the other by applying the is_subset
method and checking in both directions.
Part 2: Checking Overlap Relationships
let out = pairs.iter()
.filter(|(a,b)|
a.is_overlapping(b) || b.is_overlapping(a)
)
.count();
This part counts pairs where the ranges overlap at all by applying the is_overlapping
method and checking in both directions.
Implementation Notes
- Trait Extensions: This solution demonstrates Rust's powerful trait system by extending an existing type with new functionality.
- Generic Programming: The trait implementation works with any ordered type, not just the specific integers used in this problem.
- Pattern Matching: The solution uses Rust's pattern matching to destructure the parsed values into range pairs.
- Error Handling: The solution uses
expect
andunwrap_or_else
for error handling, though a more robust solution might handle errors more gracefully.
The implementation is concise and idiomatic, leveraging Rust's type system and functional programming features to solve the problem elegantly.