From f38a0efafb1159e1a2a3fe5f6a6c2d34529c0398 Mon Sep 17 00:00:00 2001 From: Kevin Caffrey Date: Wed, 6 Dec 2023 11:17:30 -0500 Subject: [PATCH] Day 6 --- README.md | 11 ++-- data/examples/06.txt | 2 + src/bin/06.rs | 134 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 data/examples/06.txt create mode 100644 src/bin/06.rs diff --git a/README.md b/README.md index 4f6716d..5596a28 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,12 @@ Solutions for [Advent of Code](https://adventofcode.com/) in [Rust](https://www. | Day | Part 1 | Part 2 | | :---: | :---: | :---: | -| [Day 1](./src/bin/01.rs) | `39.5µs` | `80.3µs` | -| [Day 2](./src/bin/02.rs) | `43.9µs` | `43.0µs` | -| [Day 3](./src/bin/03.rs) | `117.0µs` | `112.6µs` | -| [Day 4](./src/bin/04.rs) | `52.3µs` | `55.7µs` | -| [Day 5](./src/bin/05.rs) | `23.5µs` | `26.9µs` | +| [Day 1](./src/bin/01.rs) | `37.5µs` | `80.3µs` | +| [Day 2](./src/bin/02.rs) | `43.8µs` | `42.8µs` | +| [Day 3](./src/bin/03.rs) | `115.0µs` | `113.1µs` | +| [Day 4](./src/bin/04.rs) | `52.4µs` | `55.1µs` | +| [Day 5](./src/bin/05.rs) | `23.4µs` | `27.1µs` | +| [Day 6](./src/bin/06.rs) | `193.0ns` | `105.0ns` | **Total: 0.59ms** diff --git a/data/examples/06.txt b/data/examples/06.txt new file mode 100644 index 0000000..b39f49d --- /dev/null +++ b/data/examples/06.txt @@ -0,0 +1,2 @@ +Time: 7 15 30 +Distance: 9 40 200 \ No newline at end of file diff --git a/src/bin/06.rs b/src/bin/06.rs new file mode 100644 index 0000000..7ac6cf8 --- /dev/null +++ b/src/bin/06.rs @@ -0,0 +1,134 @@ +use std::str::FromStr; + +advent_of_code::solution!(6); + +pub fn part_one(input: &str) -> Option { + let races = input.parse::().expect("input should parse"); + races + .races + .into_iter() + .map(Race::number_of_ways_to_beat) + .reduce(|acc, n| acc * n) +} + +pub fn part_two(input: &str) -> Option { + let (time_str, distance_str) = input.split_once('\n')?; + let time = time_str + .split_once(':')? + .1 + .chars() + .filter_map(|ch| ch.to_digit(10).map(u64::from)) + .reduce(|acc, d| acc * 10 + d)?; + let distance = distance_str + .split_once(':')? + .1 + .chars() + .filter_map(|ch| ch.to_digit(10).map(u64::from)) + .reduce(|acc, d| acc * 10 + d)?; + let race = Race { + time, + best_distance: distance, + }; + Some(race.number_of_ways_to_beat()) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct Races { + races: Vec, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +struct Race { + time: u64, + best_distance: u64, +} + +impl Race { + pub fn number_of_ways_to_beat(self) -> u64 { + // If we charge for charge_time (charge_time <= race_time), then + // the velocity (v) at the end will be v = charge_time. + // We then have (race_time - charge_time) left in the race, + // and distance = v * t = charge_time * (race_time - charge_time). + // + // We want to solve for distance > best_distance. If we solve for equality, + // we get charge_time^2 - race_time*charge_time + best_distance = 0. + // + // Solving for charge_time using the quadratic formula results in + // charge_time = 0.5 * (race_time +/- sqrt(race_time^2 - 4*best_distance)) + // We can compute these two bounds (taking the floor of the upper bound and ceiling + // of the lower bound), check the value for the distance at those two points, + // and if necessary shrink the range. This should then result in our answer: + // upper_bound - lower_bound + 1 + // + let rt = self.time as f64; + let bd = self.best_distance as f64; + let mut lower_bound = (0.5 * (rt - (rt * rt - 4. * bd).sqrt())).ceil().max(0.) as u64; + let mut upper_bound = (0.5 * (rt + (rt * rt - 4. * bd).sqrt())).floor().min(rt) as u64; + if self.calculate_distance(lower_bound) <= self.best_distance { + lower_bound += 1; + } + if self.calculate_distance(upper_bound) <= self.best_distance { + upper_bound -= 1; + } + if upper_bound < lower_bound { + return 0; + } + upper_bound - lower_bound + 1 + } + + pub fn calculate_distance(self, charge_time: u64) -> u64 { + // See above + charge_time * (self.time - charge_time) + } +} + +#[derive(Debug)] +struct ParseRacesErr; + +impl FromStr for Races { + type Err = ParseRacesErr; + + fn from_str(s: &str) -> Result { + let (times_str, distances_str) = s.split_once('\n').ok_or(ParseRacesErr)?; + let times = times_str + .split_once(':') + .ok_or(ParseRacesErr)? + .1 + .split_whitespace() + .map(|s| s.parse::().map_err(|_| ParseRacesErr)); + let distances = distances_str + .split_once(':') + .ok_or(ParseRacesErr)? + .1 + .split_whitespace() + .map(|s| s.parse::().map_err(|_| ParseRacesErr)); + Ok(Self { + races: times + .zip(distances) + .map(|(t, d)| { + Ok(Race { + time: t?, + best_distance: d?, + }) + }) + .collect::, ParseRacesErr>>()?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_part_one() { + let result = part_one(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(288)); + } + + #[test] + fn test_part_two() { + let result = part_two(&advent_of_code::template::read_file("examples", DAY)); + assert_eq!(result, Some(71503)); + } +}