use aoc23::prelude::*; use derive_builder::Builder; use itertools::Itertools; use std::{cmp::Ordering, str::FromStr, time::Instant}; #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)] enum Strength { HighCard, OnePair, TwoPair, ThreeOfAKind, FullHouse, FourOfAKind, FiveOfAKind, } impl Strength { #[cfg(feature = "part2")] fn for_hand(hand: &Hand) -> Self { let mut counts = [0; 15]; let mut jokers = 0; for card in hand.cards.iter() { if card == &1 { jokers += 1; } else { counts[*card as usize - 2] += 1; } } let mut strength = counts .iter() .fold(Strength::HighCard, |strength, &count| match count { 5 => Strength::FiveOfAKind, 4 => Strength::FourOfAKind, 3 => match strength { Strength::TwoPair => Strength::FullHouse, Strength::OnePair => Strength::FullHouse, _ => Strength::ThreeOfAKind, }, 2 => match strength { Strength::ThreeOfAKind => Strength::FullHouse, Strength::OnePair => Strength::TwoPair, _ => Strength::OnePair, }, _ => strength, }); while jokers > 0 { strength = match strength { Strength::HighCard => Strength::OnePair, Strength::OnePair => Strength::ThreeOfAKind, Strength::TwoPair => Strength::FullHouse, Strength::ThreeOfAKind => Strength::FourOfAKind, Strength::FullHouse => Strength::FourOfAKind, Strength::FourOfAKind => Strength::FiveOfAKind, Strength::FiveOfAKind => Strength::FiveOfAKind, }; jokers -= 1; } strength } #[cfg(feature = "part1")] fn for_hand(hand: &Hand) -> Self { let mut counts = [0; 15]; for card in hand.cards.iter() { counts[*card as usize - 2] += 1; } counts .iter() .fold(Strength::HighCard, |strength, &count| match count { 5 => Strength::FiveOfAKind, 4 => Strength::FourOfAKind, 3 => match strength { Strength::TwoPair => Strength::FullHouse, Strength::OnePair => Strength::FullHouse, _ => Strength::ThreeOfAKind, }, 2 => match strength { Strength::ThreeOfAKind => Strength::FullHouse, Strength::OnePair => Strength::TwoPair, _ => Strength::OnePair, }, _ => strength, }) } } #[derive(Debug, Builder, Clone, PartialEq, Eq)] struct Hand { cards: Vec, bid: usize, } impl Ord for Hand { fn cmp(&self, other: &Self) -> std::cmp::Ordering { let self_strength = Strength::for_hand(self); let other_strength = Strength::for_hand(other); match self_strength.cmp(&other_strength) { Ordering::Equal => self .cards .iter() .zip(other.cards.iter()) .map(|(s, o)| s.cmp(o)) .skip_while(|ord| matches!(ord, Ordering::Equal)) .next() .unwrap_or(Ordering::Equal), Ordering::Less => Ordering::Less, Ordering::Greater => Ordering::Greater, } } } impl PartialOrd for Hand { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl FromStr for Hand { type Err = BoxE; fn from_str(s: &str) -> std::result::Result { let mut splits = s.trim().split_whitespace(); Ok(HandBuilder::default() .cards( splits .next() .unwrap() .chars() .map(|c| { if let Some(digit) = c.to_digit(10) { digit } else { match c { #[cfg(feature = "part1")] 'A' => 14, #[cfg(feature = "part2")] 'A' => 13, #[cfg(feature = "part1")] 'K' => 13, #[cfg(feature = "part2")] 'K' => 12, #[cfg(feature = "part1")] 'Q' => 12, #[cfg(feature = "part2")] 'Q' => 11, #[cfg(feature = "part1")] 'J' => 11, #[cfg(feature = "part2")] 'J' => 1, 'T' => 10, _ => panic!("invalid card: {}", c), } } }) .collect(), ) .bid(splits.next().unwrap().parse()?) .build()?) } } fn calculate(input: String) -> Result { let start = Instant::now(); let answer = input .lines() .map(|line| line.parse::().unwrap()) .sorted() .enumerate() .map(|(idx, hand)| hand.bid * (idx + 1)) .sum(); let algo_time = start.elapsed(); println!("Day {DAY}: {answer}"); println!("\t{algo_time:?}"); Ok(answer) } // TODO come back and revise for a faster solution #[tokio::main] async fn main() -> Result<()> { println!("Day {DAY}"); println!("====="); let input = utils::aoc::get_puzzle_input(DAY).await?; calculate(input.clone())?; Ok(()) } static DAY: u8 = 7; #[cfg(test)] mod tests { use super::*; static DATA: &'static str = "32T3K 765 T55J5 684 KK677 28 KTJJT 220 QQQJA 483"; #[test] fn test() -> Result<()> { #[cfg(feature = "part1")] assert_eq!(calculate(DATA.to_owned())?, 6440); #[cfg(feature = "part2")] assert_eq!(calculate(DATA.to_owned())?, 5905); Ok(()) } }