use std::{cmp::Reverse, collections::BTreeMap, str::FromStr}; use anyhow::Context; use crate::{Problem, Solution}; pub struct Day07; impl Problem for Day07 { const DAY: u8 = 7; const INPUT: &'static str = include_str!("../input/day_07.txt"); } impl Solution for Day07 { type Answer1 = usize; type Answer2 = usize; fn part_1(input: &str) -> anyhow::Result { let hand_bets = input .lines() .map(|s| { s.trim() .split_once(' ') .with_context(|| format!("Invalid had format: {s}")) .and_then(|(hand, bet)| anyhow::Ok((Reverse(hand.parse()?), bet.parse()?))) }) .try_collect::>, usize>>()?; Ok(hand_bets .into_values() .enumerate() .map(|(i, bid)| bid * (i + 1)) .sum()) } fn part_2(input: &str) -> anyhow::Result { let hand_bets = input .lines() .map(|s| { s.trim() .split_once(' ') .with_context(|| format!("Invalid had format: {s}")) .and_then(|(hand, bet)| anyhow::Ok((Reverse(hand.parse()?), bet.parse()?))) }) .try_collect::>, usize>>()?; Ok(hand_bets .into_values() .enumerate() .map(|(i, bid)| bid * (i + 1)) .sum()) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] enum Hand { FiveOfAKind([Card; 5]), FourOfAKind([Card; 5]), FullHouse([Card; 5]), ThreeOfAKind([Card; 5]), TwoPair([Card; 5]), OnePair([Card; 5]), HighCard([Card; 5]), } impl Hand { fn count_types(mut cards: [Card; 5]) -> (Vec, usize) { cards.sort(); cards .array_windows() .rev() .fold((Vec::from([1]), 0), |(mut acc, mut wilds), arr| { match arr { [Card::Joker, Card::Joker] if wilds == 0 => wilds += 1, [_, Card::Joker] => wilds += 1, [p, n] if p == n => *acc.last_mut().unwrap() += 1, _ => acc.push(1), } (acc, wilds) }) } } impl From<[Card; 5]> for Hand { fn from(cards: [Card; 5]) -> Self { let (mut counts, wilds) = Hand::count_types(cards); counts.sort(); let n = counts.pop().unwrap(); let has_2 = counts.pop().is_some_and(|m| m == 2); match (n + wilds).min(5) { 5 => Self::FiveOfAKind(cards), 4 => Self::FourOfAKind(cards), 3 if has_2 => Self::FullHouse(cards), 3 => Self::ThreeOfAKind(cards), 2 if has_2 => Self::TwoPair(cards), 2 => Self::OnePair(cards), _ => Self::HighCard(cards), } } } impl FromStr for Hand { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let cards: [Card; 5] = s .chars() .map(Card::try_from) .try_collect::>()? .try_into() .map_err(|v| anyhow::format_err!("Incorrect number of cards: {v:?}"))?; Ok(Self::from(cards)) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] enum Card { Ace, King, Queen, Jack, Ten, Nine, Eight, Seven, Six, Five, Four, Three, Two, Joker, } impl TryFrom for Card { type Error = anyhow::Error; fn try_from(value: char) -> Result { Ok(match value { 'A' => Card::Ace, 'K' => Card::King, 'Q' => Card::Queen, 'J' if WILD => Card::Joker, 'J' => Card::Jack, 'T' => Card::Ten, '9' => Card::Nine, '8' => Card::Eight, '7' => Card::Seven, '6' => Card::Six, '5' => Card::Five, '4' => Card::Four, '3' => Card::Three, '2' => Card::Two, c => anyhow::bail!("Failed to parse card value: {c}"), }) } } #[cfg(test)] mod tests { use super::*; use pretty_assertions::assert_eq; const INPUT: &str = indoc::indoc! {" 32T3K 765 T55J5 684 KK677 28 KTJJT 220 QQQJA 483 "}; #[test] fn test_part_1() -> anyhow::Result<()> { Ok(assert_eq!(6440, Day07::part_1(INPUT)?)) } #[test] fn test_part_2() -> anyhow::Result<()> { Ok(assert_eq!(5905, Day07::part_2(INPUT)?)) } }