diff --git a/src/lib/solutions/year_2023/day_04.rs b/src/lib/solutions/year_2023/day_04.rs new file mode 100644 index 0000000..e5ca78f --- /dev/null +++ b/src/lib/solutions/year_2023/day_04.rs @@ -0,0 +1,134 @@ +use std::{collections::HashSet, str::FromStr}; + +use crate::utils::solution::Solution; + +pub struct Day04 {} + +impl Solution for Day04 { + fn part_one(&self, input: &str) -> Option { + Scratchcards::from_str(input) + .map(|sc| sc.total_points().to_string()) + .ok() + } + + fn part_two(&self, input: &str) -> Option { + Scratchcards::from_str(input) + .map(|sc| sc.total_cards().to_string()) + .ok() + } +} + +struct Scratchcards(Vec); + +impl Scratchcards { + fn total_points(&self) -> i32 { + self.0.iter().map(|card| card.points()).sum::() + } + + fn total_cards(&self) -> i32 { + let mut copies = vec![1; self.0.len()]; + + for i in 0..self.0.len() { + let n = self.0[i].num_winning_numbers() as usize; + + for j in (i + 1)..=(i + n) { + copies[j] += copies[i]; + } + } + + copies.iter().sum::() + } +} + +impl FromStr for Scratchcards { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + Ok(Self( + s.lines() + .filter_map(|line| { + line.split_once(':') + .map(|(_, c)| Card::from_str(c).unwrap()) + }) + .collect(), + )) + } +} + +type Numbers = HashSet; +struct Card { + winning_numbers: Numbers, + have_numbers: Numbers, +} + +impl Card { + fn new(winning_numbers: Numbers, have_numbers: Numbers) -> Self { + Self { + winning_numbers, + have_numbers, + } + } + + fn num_winning_numbers(&self) -> i32 { + self.winning_numbers + .intersection(&self.have_numbers) + .count() as i32 + } + + fn points(&self) -> i32 { + let n = self.num_winning_numbers(); + match n { + 0 => 0, + _ => 2_i32.pow((n - 1) as u32), + } + } +} + +impl FromStr for Card { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + if let Some((winning, have)) = s.split_once('|') { + Ok(Card::new( + winning + .split_whitespace() + .flat_map(str::parse::) + .collect(), + have.split_whitespace() + .flat_map(str::parse::) + .collect(), + )) + } else { + Err("bad card string") + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn example() { + const INPUT: &str = "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 +"; + let scratchcards = Scratchcards::from_str(INPUT).expect("failed to parse input"); + + assert_eq!(scratchcards.0[0].points(), 8); + assert_eq!(scratchcards.0[1].points(), 2); + assert_eq!(scratchcards.0[2].points(), 2); + assert_eq!(scratchcards.0[3].points(), 1); + assert_eq!(scratchcards.0[4].points(), 0); + assert_eq!(scratchcards.0[5].points(), 0); + + assert_eq!(scratchcards.total_points(), 13); + assert_eq!(scratchcards.total_cards(), 30); + } +} + +crate::verify!(Day04, crate::my_input!("2023", "Day04"), "15268", "6283755"); diff --git a/src/lib/solutions/year_2023/mod.rs b/src/lib/solutions/year_2023/mod.rs index b08588d..90bcc49 100644 --- a/src/lib/solutions/year_2023/mod.rs +++ b/src/lib/solutions/year_2023/mod.rs @@ -3,12 +3,14 @@ use super::DayAssociations; pub mod day_01; pub mod day_02; pub mod day_03; +pub mod day_04; pub fn solutions() -> DayAssociations { let associations: Vec<(i32, super::BoxedSolution)> = vec![ (1, Box::new(day_01::Day01 {})), (2, Box::new(day_02::Day02 {})), (3, Box::new(day_03::Day03 {})), + (4, Box::new(day_04::Day04 {})), ]; associations.into_iter().collect()