From feaf58b509ae53bebe7ff5bc3d8a13af9c4c989b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20K=C3=B6rber?= Date: Mon, 4 Dec 2023 11:33:28 +0100 Subject: [PATCH] Add day 4 --- 2023/day4/.gitignore | 1 + 2023/day4/Cargo.lock | 39 +++++++++ 2023/day4/Cargo.toml | 10 +++ 2023/day4/src/main.rs | 189 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 239 insertions(+) create mode 100644 2023/day4/.gitignore create mode 100644 2023/day4/Cargo.lock create mode 100644 2023/day4/Cargo.toml create mode 100644 2023/day4/src/main.rs diff --git a/2023/day4/.gitignore b/2023/day4/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/2023/day4/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/2023/day4/Cargo.lock b/2023/day4/Cargo.lock new file mode 100644 index 0000000..ed70afb --- /dev/null +++ b/2023/day4/Cargo.lock @@ -0,0 +1,39 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "day4" +version = "0.1.0" +dependencies = [ + "indoc", + "nom", +] + +[[package]] +name = "indoc" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" + +[[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", +] diff --git a/2023/day4/Cargo.toml b/2023/day4/Cargo.toml new file mode 100644 index 0000000..ddfd2b3 --- /dev/null +++ b/2023/day4/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "day4" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +indoc = "2.0.4" +nom = "7.1.3" diff --git a/2023/day4/src/main.rs b/2023/day4/src/main.rs new file mode 100644 index 0000000..f2c5aef --- /dev/null +++ b/2023/day4/src/main.rs @@ -0,0 +1,189 @@ +use std::cmp::min; + +use nom::{ + bytes::complete::tag, + character::complete::char, + multi::{many1, separated_list1}, + sequence::{preceded, separated_pair, terminated, tuple}, +}; + +#[derive(Debug, PartialEq, Eq)] +struct Card { + id: usize, + winning_numbers: Vec, + have_numbers: Vec, +} + +mod parser { + use nom::{ + branch::alt, + bytes::complete::take_while1, + character::complete::{char, digit1}, + combinator::map, + multi::many_m_n, + sequence::preceded, + IResult, + }; + + pub fn double_digit_number(i: &str) -> IResult<&str, usize> { + let single_digit = preceded(char(' '), many_m_n(1, 1, digit1)); + let double_digit = many_m_n(1, 1, digit1); + + let digit = alt((single_digit, double_digit)); + + let mut digit = map(digit, |chars| { + chars + .into_iter() + .collect::() + .parse::() + .unwrap() + }); + + digit(i) + } + + pub fn number(i: &str) -> IResult<&str, usize> { + map( + take_while1::<_, &str, _>(|c| c.is_ascii_digit()), + |s: &str| s.parse::().unwrap(), + )(i) + } + + #[cfg(test)] + mod tests { + use super::*; + + #[test] + fn parse_double_digit() { + assert_eq!(double_digit_number("23").unwrap(), ("", 23)); + assert_eq!(double_digit_number(" 3").unwrap(), ("", 3)); + assert_eq!(double_digit_number("3 ").unwrap(), (" ", 3)); + assert!(double_digit_number(" 3").is_err()); + } + + #[test] + fn parse_number() { + assert_eq!(number("23").unwrap(), ("", 23)); + assert_eq!(number("2").unwrap(), ("", 2)); + assert_eq!(number("2x").unwrap(), ("x", 2)); + assert!(number(" 3").is_err()); + } + } +} + +impl Card { + fn parse(input: &str) -> Self { + let card_prefix = tuple((tag("Card"), many1(char(' ')))); + + let winning_numbers = separated_list1(char(' '), parser::double_digit_number); + let have_numbers = separated_list1(char(' '), parser::double_digit_number); + + let numbers = separated_pair(winning_numbers, tag(" | "), have_numbers); + + let card_info = terminated(preceded(card_prefix, parser::number), char(':')); + + let mut card = separated_pair(card_info, char(' '), numbers); + + let (rest, (card_id, (winning_numbers, have_numbers))) = card(input).unwrap(); + + assert!(rest.is_empty()); + + Self { + id: card_id, + winning_numbers, + have_numbers, + } + } + + fn matching_numbers(&self) -> Vec { + self.have_numbers + .iter() + .filter(|n| self.winning_numbers.contains(n)) + .copied() + .collect() + } + + fn value(&self) -> usize { + let matches = self.matching_numbers().len(); + if matches == 0 { + 0 + } else { + 2_usize.pow(matches as u32 - 1) + } + } +} + +fn part1(input: &str) -> usize { + input + .lines() + .map(Card::parse) + .map(|card| card.value()) + .sum() +} + +fn part2(input: &str) -> usize { + let cards: Vec = input.lines().map(Card::parse).collect(); + let mut card_count: Vec = std::iter::repeat(1).take(cards.len()).collect(); + + for i in 0..cards.len() { + let matches = cards[i].matching_numbers().len(); + for j in (i + 1)..min(i + matches + 1, cards.len()) { + card_count[j] += card_count[i]; + } + } + card_count.into_iter().sum() +} + +fn main() { + let input = include_str!("../input"); + + println!("Part 1 : {}", part1(input)); + println!("Part 2 : {}", part2(input)); +} + +#[cfg(test)] +mod tests { + use super::*; + use indoc::indoc; + + #[test] + fn example_01() { + let input = indoc! {" + Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 + Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 + Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 + Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 + Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 + Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 + "}; + + assert_eq!(part1(input), 13); + } + + #[test] + fn parse_card() { + let input = "Card 1: 41 48 83 6 17 | 83 86 6 31 17 9 48 53"; + assert_eq!( + Card::parse(input), + Card { + id: 1, + winning_numbers: vec![41, 48, 83, 6, 17], + have_numbers: vec![83, 86, 6, 31, 17, 9, 48, 53], + } + ); + } + + #[test] + fn example_02() { + let input = indoc! {" + Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53 + Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19 + Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1 + Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83 + Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36 + Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11 + "}; + + assert_eq!(part2(input), 30); + } +}