diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3badaa1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,50 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +#Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ +.vscode/settings.json + +input.txt diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..15c47f9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,151 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "day-1" +version = "2023.0.0" +dependencies = [ + "derive-getters", + "error-stack", + "itertools", + "log", + "nom", +] + +[[package]] +name = "day-2" +version = "2023.0.0" +dependencies = [ + "itertools", + "log", + "nom", +] + +[[package]] +name = "day-n" +version = "2023.0.0" +dependencies = [ + "itertools", + "nom", +] + +[[package]] +name = "derive-getters" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2c35ab6e03642397cdda1dd58abbc05d418aef8e36297f336d5aba060fe8df" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "error-stack" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27a72baa257b5e0e2de241967bc5ee8f855d6072351042688621081d66b2a76b" +dependencies = [ + "anyhow", + "rustc_version", +] + +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "proc-macro2" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/day-2/Cargo.toml b/day-2/Cargo.toml new file mode 100644 index 0000000..ed23618 --- /dev/null +++ b/day-2/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "day-2" +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 +log.workspace = true diff --git a/day-2/src/lib.rs b/day-2/src/lib.rs new file mode 100644 index 0000000..d807ddf --- /dev/null +++ b/day-2/src/lib.rs @@ -0,0 +1,4 @@ +pub mod part1; +pub use crate::part1::part1; +pub mod part2; +pub use crate::part2::part2; diff --git a/day-2/src/main.rs b/day-2/src/main.rs new file mode 100644 index 0000000..5950776 --- /dev/null +++ b/day-2/src/main.rs @@ -0,0 +1,12 @@ +#![warn(clippy::all, clippy::pedantic)] + +use day_2::part1; +use day_2::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/day-2/src/part1.rs b/day-2/src/part1.rs new file mode 100644 index 0000000..aab49aa --- /dev/null +++ b/day-2/src/part1.rs @@ -0,0 +1,106 @@ +use log::debug; +use nom::{ + bytes::complete::tag, + character::complete::{self, newline}, + multi::separated_list1, + sequence::{preceded, separated_pair}, +}; + +#[derive(Debug)] +struct Round { + pub red_n: u32, + pub green_n: u32, + pub blue_n: u32, +} + +#[derive(Debug)] +struct Game { + pub id: u32, + pub rounds: Vec, +} + +impl Game { + fn to_part1(&self) -> Option { + if self + .rounds + .iter() + .find_map(|r| { + //TODO if inverted use find_map + if r.red_n > 12 || r.green_n > 13 || r.blue_n > 14 { + Some(self.id) + } else { + None + } + }) + .is_some() + { + None + } else { + Some(self.id) + } + } +} + +pub fn part1(input: &str) -> String { + let (_, games) = process_input(input).expect("there should be input"); + debug!("{games:?}"); + games + .iter() + .filter_map(Game::to_part1) + .sum::() + .to_string() +} + +fn process_block(input: &str) -> nom::IResult<&str, (u32, String)> { + let (i, (cnt, color)) = + separated_pair(complete::u32, complete::space1, complete::alpha1)(input)?; + Ok((i, (cnt, color.to_owned()))) +} + +fn process_round(input: &str) -> nom::IResult<&str, Round> { + let (i, blocks) = separated_list1(tag(", "), process_block)(input)?; + let mut round = Round { + red_n: 0, + green_n: 0, + blue_n: 0, + }; + for (cnt, color) in blocks { + match color.as_str() { + "red" => round.red_n = cnt, + "green" => round.green_n = cnt, + "blue" => round.blue_n = cnt, + _ => panic!("this should be a color name"), + }; + } + Ok((i, round)) +} + +fn process_game(input: &str) -> nom::IResult<&str, Game> { + let (i, (id, rounds)) = separated_pair( + preceded(tag("Game "), complete::u32), + tag(": "), + separated_list1(tag("; "), process_round), + )(input)?; + Ok((i, Game { id, rounds })) +} + +fn process_input(input: &str) -> nom::IResult<&str, Vec> { + separated_list1(newline, process_game)(input) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green +Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue +Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red +Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red +Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green"; + + #[test] + fn part1_works() { + let result = part1(INPUT); + assert_eq!(result, "8".to_string()); + } +} diff --git a/day-2/src/part2.rs b/day-2/src/part2.rs new file mode 100644 index 0000000..298c34a --- /dev/null +++ b/day-2/src/part2.rs @@ -0,0 +1,97 @@ +use nom::{ + bytes::complete::tag, + character::complete::{self, newline}, + multi::separated_list1, + sequence::{preceded, separated_pair}, +}; + +#[derive(Debug)] +struct Round { + pub red_n: u32, + pub green_n: u32, + pub blue_n: u32, +} + +#[derive(Debug)] +struct Game { + pub _id: u32, + pub rounds: Vec, +} + +impl Game { + fn to_power(&self) -> u64 { + let (r, g, b) = self.rounds.iter().fold((0_u64, 0_u64, 0_u64), |acc, x| { + let (mut val_r, mut val_g, mut val_b) = acc; + if u64::from(x.red_n) > acc.0 { + val_r = x.red_n.into(); + } + if u64::from(x.green_n) > acc.1 { + val_g = x.green_n.into(); + } + if u64::from(x.blue_n) > acc.2 { + val_b = x.blue_n.into(); + } + (val_r, val_g, val_b) + }); + r * g * b + } +} + +pub fn part2(input: &str) -> String { + let (_, games) = process_input(input).expect("there should be input"); + games.iter().map(Game::to_power).sum::().to_string() +} + +fn process_block(input: &str) -> nom::IResult<&str, (u32, String)> { + let (i, (cnt, color)) = + separated_pair(complete::u32, complete::space1, complete::alpha1)(input)?; + Ok((i, (cnt, color.to_owned()))) +} + +fn process_round(input: &str) -> nom::IResult<&str, Round> { + let (i, blocks) = separated_list1(tag(", "), process_block)(input)?; + let mut round = Round { + red_n: 0, + green_n: 0, + blue_n: 0, + }; + for (cnt, color) in blocks { + match color.as_str() { + "red" => round.red_n = cnt, + "green" => round.green_n = cnt, + "blue" => round.blue_n = cnt, + _ => panic!("this should be a color name"), + }; + } + Ok((i, round)) +} + +fn process_game(input: &str) -> nom::IResult<&str, Game> { + let (i, (id, rounds)) = separated_pair( + preceded(tag("Game "), complete::u32), + tag(": "), + separated_list1(tag("; "), process_round), + )(input)?; + Ok((i, Game { _id: id, rounds })) +} + +fn process_input(input: &str) -> nom::IResult<&str, Vec> { + separated_list1(newline, process_game)(input) +} + +#[cfg(test)] +mod test { + use super::*; + + const INPUT: &str = "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green +Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue +Game 3: 8 green, 6 blue, 20 red; 5 blue, 4 red, 13 green; 5 green, 1 red +Game 4: 1 green, 3 red, 6 blue; 3 green, 6 red; 3 green, 15 blue, 14 red +Game 5: 6 red, 1 blue, 3 green; 2 blue, 1 red, 2 green"; + + #[test] + fn part2_works() { + let result = part2(INPUT); + assert_eq!(result, "2286".to_string()); + } +}