Advent of Code 2023 - Day 6
I am happy to tell that Day 6 was indeed easier than Day 5, let’s make this quick.
You can find the final code @ advent-of-code/2023/day6
We have the following input
Time: 7 15 30
Distance: 9 40 200
The first line lists the total time that is allowed for a race and the second line contains the record distance that has been recorded for that race.
Your toy boat has a starting speed of zero millimeters per millisecond. For each whole millisecond you spend at the beginning of the race holding down the button, the boat’s speed increases by one millimeter per millisecond.
We can easily solve this with a bit of calculus and math! Let’s take the second race for example, there has been a max recorded distance of 40mm and the allowed time for that race is 15ms.
We can model this problem with a second grade inequality function of this type:
x * (15 - x) > 40
which equates to
x^2 -15x + 40 < 0
This inequality indicates all the different values possible to end up with a
greater distance of 40mm for that race, in our case we only have to consider
discrete values. If we draw that function we’ll see a parabola facing upward and
intersecting y = 0
in two different points, those are the min and max values
of all the different values that will take us further than 40mm.
Take a look at the function in WolframAlpha
As usual, I’d like to create a struct to represent an equation of type ax^2 + bx + c
:
struct Eq {
a: f64,
b: f64,
c: f64,
}
We have our equation, we need a function that calculates the intersection points
with y = 0
and return that as our range of possible values.
impl Eq {
fn new(a: f64, b: f64, c: f64) -> Self {
Self { a, b, c }
}
fn range(&self) -> (i32, i32) {
let discriminant = self.b * self.b - 4.0 * self.a * self.c;
if discriminant >= 0.0 {
let mut root1 = (-self.b + discriminant.sqrt()) / (2.0 * self.a);
let mut root2 = (-self.b - discriminant.sqrt()) / (2.0 * self.a);
// need to be strictly greater, so we have to remove this
// value in case root1 coincides with a discrete value
if root1.fract() == 0.0 {
root1 -= 1.0;
}
// same here
if root2.fract() == 0.0 {
root2 += 1.0;
}
return (root2.ceil() as i32, root1.floor() as i32);
}
(0, 0)
}
}
We have everything we need at this point, we just need to parse our input, calculate every range of possible values for each race and multiply all the final values together to get our result.
fn part1(input: &str) -> io::Result<u32> {
let lines: Vec<&str> = input.lines().collect::<Vec<&str>>();
let ms: Vec<f64> = lines[0]
.split(" ")
.skip(1)
.filter(|x| !x.is_empty())
.flat_map(|x| x.parse())
.collect();
let records: Vec<f64> = lines[1]
.split(" ")
.skip(1)
.filter(|x| !x.is_empty())
.flat_map(|x| x.parse())
.collect();
let mut eqs = Vec::new();
for i in 0..ms.len() {
eqs.push(Eq::new(1.0, -ms[i], records[i]));
}
let res = eqs
.into_iter()
.map(|x| x.range())
.map(|(lower, upper)| upper - lower + 1)
.reduce(|acc, e| acc * e)
.unwrap();
Ok(res as u32)
}
Quick test
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part1() {
assert_eq!(
(4 * 8 * 9),
part1("Time: 7 15 30\nDistance: 9 40 200").unwrap()
)
}
}
Solution is correct, nice and easy, let’s move on.
There’s really only one race - ignore the spaces between the numbers on each line.
Lucky me, we don’t have to change any logic but the parting one in this case. Our input now has to be considered as a single time number and a single distance number, that’s it!
fn part2(input: &str) -> io::Result<u32> {
let lines: Vec<&str> = input.lines().collect::<Vec<&str>>();
let ms: f64 = lines[0]
.split(" ")
.skip(1)
.filter(|x| !x.is_empty())
.collect::<String>()
.parse()
.unwrap();
let record: f64 = lines[1]
.split(" ")
.skip(1)
.filter(|x| !x.is_empty())
.collect::<String>()
.parse()
.unwrap();
let eq = Eq::new(1.0, -ms, record);
let (lower, upper) = eq.range();
Ok(upper as u32 - lower as u32 + 1)
}
Testing testing testing
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_part2() {
assert_eq!(
71503,
part2("Time: 7 15 30\nDistance: 9 40 200").unwrap()
)
}
}
Easy done, calculus makes everything fun, right?