diff --git a/src/day7/mod.rs b/src/day7/mod.rs new file mode 100644 index 0000000..c63c45b --- /dev/null +++ b/src/day7/mod.rs @@ -0,0 +1,217 @@ +use std::{cmp::Ordering, collections::HashMap}; + +use crate::utils::Day; + +#[derive(Debug, Default)] +pub struct Day7 { + pub input: String, + pub hands: Vec, +} + +#[derive(Debug)] +pub struct Hand { + cards: String, + bid: usize, +} + +impl Day7 { + fn classify_hand(hand: &Hand, part: u8) -> u8 { + let parsed = hand + .cards + .chars() + .fold(HashMap::::new(), |mut acc, card| { + if let Some(count) = acc.get_mut(&card) { + *count += 1; + } else { + acc.insert(card, 1); + } + + acc + }); + + let mut repetitions = parsed + .iter() + .fold(Vec::::new(), |mut acc, (card, count)| { + if !(part == 2 && card == &'J') { + acc.push(*count); + } + + acc + }); + repetitions.sort(); + repetitions.reverse(); + + match part { + 1 => match repetitions.as_slice() { + &[5] => 7, // 5 of a kind + &[4, 1] => 6, // 4 of a kind + &[3, 2] => 5, // full house + &[3, 1, 1] => 4, // 3 of a kind + &[2, 2, 1] => 3, // 2 pair + &[2, 1, 1, 1] => 2, // 1 pair + &[1, 1, 1, 1, 1] => 1, // high card + _ => 0, + }, + 2 => { + let jokers = *parsed.get(&'J').unwrap_or(&0); + + if Self::fill_repetitions_with_jokers(repetitions.clone(), jokers, 7) == &[5] { + return 7; + } // 5 of a kind + if Self::fill_repetitions_with_jokers(repetitions.clone(), jokers, 6) == &[4, 1] { + return 6; + } // 4 of a kind + if Self::fill_repetitions_with_jokers(repetitions.clone(), jokers, 5) == &[3, 2] { + return 5; + } // full house + if Self::fill_repetitions_with_jokers(repetitions.clone(), jokers, 4) == &[3, 1, 1] { + return 4; + } // 3 of a kind + if Self::fill_repetitions_with_jokers(repetitions.clone(), jokers, 3) == &[2, 2, 1] { + return 3; + } // 2 pair + if Self::fill_repetitions_with_jokers(repetitions.clone(), jokers, 2) == &[2, 1, 1, 1] { + return 2; + } // 1 pair + if Self::fill_repetitions_with_jokers(repetitions.clone(), jokers, 1) == &[1, 1, 1, 1, 1] { + return 1; + } // high card + + 0 + } + _ => panic!(), + } + } + + fn fill_repetitions_with_jokers( + mut repetitions: Vec, + mut jokers: u8, + class: u8, + ) -> Vec { + for _ in 0..(5 - repetitions.len()) { repetitions.push(0) } + match class { + 7 | 6 => { + repetitions[0] += jokers; + jokers = 0; + } + 5 => { + let using = (3 - repetitions[0].min(3)).min(jokers); // amnt of jokers needed for 3 repetitions + jokers -= using; + repetitions[0] += using; + + let using = (2 - repetitions[1].min(2)).min(jokers); // amnt of jokers needed for 2 repetitions + jokers -= using; + repetitions[1] += using; + } + 4 => { + let using = (3 - repetitions[0].min(3)).min(jokers); + jokers -= using; + repetitions[0] += using; + } + 3 => { + let using = (2 - repetitions[0].min(2)).min(jokers); // amnt of jokers needed for 2 repetitions + jokers -= using; + repetitions[0] += using; + + let using = (2 - repetitions[1].min(2)).min(jokers); // amnt of jokers needed for 2 repetitions + jokers -= using; + repetitions[1] += using; + } + 2 => { + let using = (2 - repetitions[0].min(2)).min(jokers); + jokers -= using; + repetitions[0] += using; + } + 1 => (), + _ => panic!(), + } + + repetitions.push(jokers); // Add unused jokers + repetitions.sort(); + repetitions.reverse(); + repetitions = repetitions.into_iter().filter(|n| n != &0).collect(); + + repetitions + } +} + +impl Day for Day7 { + fn part1(&mut self) -> String { + self.hands.clear(); + self.hands = self + .input + .lines() + .map(|line| { + let (cards, bid) = line.split_once(' ').unwrap(); + Hand { + cards: cards.to_string(), + bid: bid.parse().unwrap(), + } + }) + .collect(); + + let order = "AKQJT98765432".to_string(); + + self.hands.sort_by(|a, b| { + let (a_class, b_class) = (Self::classify_hand(a, 1), Self::classify_hand(b, 1)); + if a_class > b_class { + Ordering::Less + } else if a_class < b_class { + Ordering::Greater + } else { + let b_chars = b.cards.chars().collect::>(); + for (i, card) in a.cards.char_indices() { + if order.find(card).unwrap() < order.find(b_chars[i]).unwrap() { + return Ordering::Less; + } else if order.find(card).unwrap() > order.find(b_chars[i]).unwrap() { + return Ordering::Greater; + } else { + continue; + } + } + + panic!("TIE"); + } + }); + self.hands.reverse(); + + self.hands + .iter() + .enumerate() + .fold(0usize, |acc, (i, hand)| (i + 1) * hand.bid + acc) + .to_string() + } + + fn part2(&mut self) -> String { + let order = "AKQT98765432J".to_string(); + + self.hands.sort_by(|a, b| { + let (a_class, b_class) = (Self::classify_hand(a, 2), Self::classify_hand(b, 2)); + if a_class > b_class { + Ordering::Less + } else if a_class < b_class { + Ordering::Greater + } else { + let b_chars = b.cards.chars().collect::>(); + for (i, card) in a.cards.char_indices() { + if order.find(card).unwrap() < order.find(b_chars[i]).unwrap() { + return Ordering::Less; + } else if order.find(card).unwrap() > order.find(b_chars[i]).unwrap() { + return Ordering::Greater; + } else { + continue; + } + } + + panic!("TIE"); + } + }); + self.hands.reverse(); + + self.hands + .iter() + .enumerate() + .fold(0usize, |acc, (i, hand)| (i + 1) * hand.bid + acc) + .to_string() + } +} diff --git a/src/fetcher.rs b/src/fetcher.rs index db3ae2e..1d66ea4 100644 --- a/src/fetcher.rs +++ b/src/fetcher.rs @@ -56,7 +56,7 @@ pub async fn fetch_large(day: u8, part: u8) -> (String, Option) { ) } -pub async fn fetch_example(auth: String, day: u8, part: u8) -> (String, String) { +pub async fn fetch_example(auth: String, day: u8, part: u8) -> (String, Option) { let html = reqwest::Client::new() .get(format!("https://adventofcode.com/2023/day/{day}")) .header("Cookie", format!("session={auth}")) @@ -86,10 +86,7 @@ pub async fn fetch_example(auth: String, day: u8, part: u8) -> (String, String) let solution = dom .select(&solution_selector) .last() - .expect("Unable to find example solution for part, is auth correct?") - .inner_html() - .trim_end() - .to_string(); + .map(|s| s.inner_html().trim_end().to_string()); (input, solution) } diff --git a/src/main.rs b/src/main.rs index bcebf96..816606c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,9 @@ use std::{ use clap::{Parser, Subcommand}; -use crate::{day1::Day1, day2::Day2, day3::Day3, day4::Day4, day5::Day5, day6::Day6, utils::Day}; +use crate::{ + day1::Day1, day2::Day2, day3::Day3, day4::Day4, day5::Day5, day6::Day6, day7::Day7, utils::Day, +}; pub mod day1; pub mod day2; @@ -14,6 +16,7 @@ pub mod day3; pub mod day4; pub mod day5; pub mod day6; +pub mod day7; pub mod fetcher; pub mod utils; @@ -88,10 +91,7 @@ async fn main() { false => (fetcher::fetch_input(cli.auth, day).await, None), true => fetcher::fetch_large(day, part).await, }, - true => { - let example = fetcher::fetch_example(cli.auth, day, part).await; - (example.0, Some(example.1)) - } + true => fetcher::fetch_example(cli.auth, day, part).await }; let mut day: Box = match day { @@ -108,6 +108,10 @@ async fn main() { }), 5 => Box::new(Day5 { input }), 6 => Box::new(Day6 { input }), + 7 => Box::new(Day7 { + input, + hands: vec![], + }), _ => panic!("Invalid day #"), }; let start = std::time::Instant::now(); @@ -160,6 +164,10 @@ async fn main() { }), 5 => Box::new(Day5 { input }), 6 => Box::new(Day6 { input }), + 7 => Box::new(Day7 { + input, + hands: vec![], + }), _ => panic!("Invalid day #"), }; let mut timings = Vec::::new(); @@ -233,7 +241,7 @@ async fn main() { stylized_input.push_str("\n+"); stylized_input.push_str(&"-".repeat(input_width + 2)); stylized_input.push('+'); - println!("Input:\n{stylized_input}\nSolution: {solution}"); + println!("Input:\n{stylized_input}\nSolution: {solution:?}"); } } }