diff --git a/2023/day-6/Cargo.toml b/2023/day-6/Cargo.toml new file mode 100644 index 0000000..35431aa --- /dev/null +++ b/2023/day-6/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "day-6" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +nom.workspace = true +itertools.workspace = true diff --git a/2023/day-6/src/lib.rs b/2023/day-6/src/lib.rs new file mode 100644 index 0000000..3fafe8d --- /dev/null +++ b/2023/day-6/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::*; +pub mod part2; +pub use crate::part2::*; diff --git a/2023/day-6/src/main.rs b/2023/day-6/src/main.rs new file mode 100644 index 0000000..9b1d9c1 --- /dev/null +++ b/2023/day-6/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_6::part1; +use day_6::part2; + +fn main() { + let input = include_str!("./input.txt"); + let part1_result = part1(input); + println!("part 1: {part1_result}"); + let part2_result = part2(input); + println!("part 2: {part2_result}"); +} diff --git a/2023/day-6/src/part1.rs b/2023/day-6/src/part1.rs new file mode 100644 index 0000000..1948cbd --- /dev/null +++ b/2023/day-6/src/part1.rs @@ -0,0 +1,65 @@ +#![warn(clippy::all, clippy::pedantic)] + +use itertools::Itertools; +use nom::{ + bytes::complete::tag, + character::complete, + multi::separated_list1, + sequence::{pair, preceded}, + IResult, +}; + +#[must_use] +pub fn part1(input: &str) -> String { + let (_, races) = parse_input(input).expect("input expected"); + races + .iter() + .map(|(time, distance)| { + (0..=*time) + .filter_map(|x| { + if (time - x) * x > *distance { + Some(()) + } else { + None + } + }) + .count() + }) + .product::() + .to_string() +} + +fn parse_input(input: &str) -> IResult<&str, Vec<(u64, u64)>> { + let (input, time) = preceded( + pair(tag("Time:"), complete::space1), + separated_list1(complete::space1, complete::u64), + )(input)?; + let (input, _) = complete::line_ending(input)?; + let (input, distance) = preceded( + pair(tag("Distance:"), complete::space1), + separated_list1(complete::space1, complete::u64), + )(input)?; + + Ok(( + input, + time.iter() + .interleave(distance.iter()) + .map(|x| *x) + .tuples() + .collect(), + )) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "Time: 7 15 30 +Distance: 9 40 200"; + + #[test] + fn part1_works() { + let result = part1(INPUT); + assert_eq!(result, "288".to_string()); + } +} diff --git a/2023/day-6/src/part2.rs b/2023/day-6/src/part2.rs new file mode 100644 index 0000000..3cd2c29 --- /dev/null +++ b/2023/day-6/src/part2.rs @@ -0,0 +1,65 @@ +#![warn(clippy::all, clippy::pedantic)] + +use itertools::Itertools; +use nom::{ + bytes::complete::tag, + character::complete, + multi::separated_list1, + sequence::{pair, preceded}, + IResult, +}; + +#[must_use] +pub fn part2(input: &str) -> String { + let (_, race) = parse_input(input).expect("input expected"); + (0..=race.0) + .filter_map(|x| { + if (race.0 - x) * x > race.1 { + Some(()) + } else { + None + } + }) + .count() + .to_string() +} + +fn parse_input(input: &str) -> IResult<&str, (u64, u64)> { + let (input, time) = preceded( + pair(tag("Time:"), complete::space1), + separated_list1(complete::space1, complete::u64), + )(input)?; + let (input, _) = complete::line_ending(input)?; + let (input, distance) = preceded( + pair(tag("Distance:"), complete::space1), + separated_list1(complete::space1, complete::u64), + )(input)?; + let distance = distance + .iter() + .map(|x| x.to_string()) + .join("") + .parse::() + .expect("is a number"); + let time = time + .iter() + .map(|x| x.to_string()) + .join("") + .parse::() + .expect("is a number"); + + Ok((input, (time, distance))) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "Time: 7 15 30 +Distance: 9 40 200"; + + #[test] + fn part2_works() { + let result = part2(INPUT); + assert_eq!(result, "71503".to_string()); + } +}