From e7f641678a2cd1a8b160c7cc783e03f619e4f518 Mon Sep 17 00:00:00 2001 From: Lars Martens Date: Wed, 6 Dec 2023 14:07:46 +0100 Subject: [PATCH] Solve day 6 --- input/06-test | 2 ++ src/bin/06.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 28 ++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 input/06-test create mode 100644 src/bin/06.rs diff --git a/input/06-test b/input/06-test new file mode 100644 index 0000000..b39f49d --- /dev/null +++ b/input/06-test @@ -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..89a5da0 --- /dev/null +++ b/src/bin/06.rs @@ -0,0 +1,71 @@ +use aoc2023::{concat, parse_ws_separated}; +use itertools::Itertools; +use std::ops::RangeInclusive; + +const INPUT: &str = include_str!("../../input/06"); +const TEST_INPUT: &str = include_str!("../../input/06-test"); + +fn main() { + assert_eq!(part1(TEST_INPUT), 288); + println!("Part 1: {}", part1(INPUT)); + assert_eq!(part2(TEST_INPUT), 71503); + println!("Part 2: {}", part2(INPUT)); +} + +fn part1(input: &str) -> u64 { + parse(input).map(how_to_beat).map(len).product() +} + +fn part2(input: &str) -> u64 { + let race = parse(input).reduce(Race::concat).unwrap(); + let range = how_to_beat(race); + len(range) +} + +/// Range of time the button might be pressed to beat a record. +fn how_to_beat(race: Race) -> RangeInclusive { + let time = race.time as f64; + let record = race.record as f64; + + // The distance travelled d is dependant on the time t the button is pressed: d = time*t - t^2. + // Solve d = record for min and max t: + let offset = time * 0.5; + let add = ((-time * 0.5).powf(2.0) - record).sqrt(); + let min = offset - add; + let max = offset + add; + + // Shrink the interval to only return times to beat the record. + let min = min.floor() as u64 + 1; + let max = max.ceil() as u64 - 1; + + min..=max +} + +fn len(range: RangeInclusive) -> u64 { + range.try_len().unwrap() as u64 +} + +struct Race { + time: u64, + record: u64, +} + +impl Race { + fn concat(self, other: Self) -> Self { + Self { + time: concat(self.time, other.time), + record: concat(self.record, other.record), + } + } +} + +fn parse(input: &str) -> impl Iterator + '_ { + let mut lines = input.lines(); + let mut parse_next_line = + |prefix: &str| parse_ws_separated(lines.next().unwrap().trim_start_matches(prefix)); + let times = parse_next_line("Time: "); + let records = parse_next_line("Distance: "); + times + .zip(records) + .map(|(time, record)| Race { time, record }) +} diff --git a/src/lib.rs b/src/lib.rs index 8b13789..cac850f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,29 @@ +use std::fmt::{Debug, Display}; +use std::str::FromStr; +/// Parse a whitespace separated list of things. +/// +/// Panics on parse error. +pub fn parse_ws_separated(s: &str) -> impl Iterator + '_ +where + T: FromStr, + ::Err: Debug, +{ + s.split_ascii_whitespace().map(|s| s.parse().unwrap()) +} + +/// Concatenate two things. +/// +/// Panics if the concatenation is not be parseable. +/// +/// ```rust +/// # use aoc2023::concat; +/// assert_eq!(concat(123, 456), 123456); +/// ``` +pub fn concat(a: T, b: T) -> T +where + T: Display + FromStr, + ::Err: Debug, +{ + format!("{a}{b}").parse().unwrap() +}