use nom::{ branch::alt, character::complete::{char as nom_char, line_ending, space1, u32 as nom_u32}, combinator::{map, value}, multi::separated_list1, sequence::tuple, IResult, }; use std::{collections::BTreeMap, fs}; fn main() -> Result<(), Box> { let input = fs::read_to_string("inputs/day_7.txt")?; { let mut games_without_jokers = CardGame::parser(false)(&input).unwrap().1; games_without_jokers.sort(); dbg!(games_without_jokers.calculate_winnings()); } { let mut games_with_jokers = CardGame::parser(true)(&input).unwrap().1; games_with_jokers.sort(); dbg!(games_with_jokers.calculate_winnings()); } Ok(()) } #[derive(Debug)] struct CardGame(Vec); #[derive(Debug, PartialEq, Eq)] struct CardHand { cards: [Card; 5], bid: u32, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] struct Card(u8); #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] enum CardRank { HighCard, OnePair, TwoPair, ThreeOfAKind, FullHouse, FourOfAKind, FiveOfAKind, } impl CardGame { fn parser(with_jokers: bool) -> impl FnMut(&str) -> IResult<&str, Self> { move |input: &str| { map( separated_list1(line_ending, CardHand::parser(with_jokers)), CardGame, )(input) } } fn sort(&mut self) { self.0.sort() } fn calculate_winnings(&self) -> u32 { self.0 .iter() .enumerate() .map(|(i, hand)| hand.bid * (i as u32 + 1)) .sum() } } impl CardHand { fn parser(with_jokers: bool) -> impl FnMut(&str) -> IResult<&str, Self> { move |input: &str| { map( tuple(( Card::parser(with_jokers), Card::parser(with_jokers), Card::parser(with_jokers), Card::parser(with_jokers), Card::parser(with_jokers), space1, nom_u32, )), |(c1, c2, c3, c4, c5, _, bid)| CardHand { cards: [c1, c2, c3, c4, c5], bid, }, )(input) } } fn card_rank(&self) -> CardRank { let mut cards_set: BTreeMap = BTreeMap::new(); for card in &self.cards { *cards_set.entry(*card).or_insert(0) += 1; } let jokers = cards_set.get(&Card(1)).cloned().unwrap_or(0); cards_set.remove(&Card(1)); let mut card_counts: Vec = cards_set.into_values().collect(); card_counts.sort_by(|a, b| b.cmp(a)); if card_counts.len() == 0 { // all 5 were jokers! CardRank::FiveOfAKind } else { card_counts[0] += jokers; if card_counts[0] == 5 { CardRank::FiveOfAKind } else if card_counts[0] == 4 { CardRank::FourOfAKind } else if card_counts[0] == 3 && card_counts[1] == 2 { CardRank::FullHouse } else if card_counts[0] == 3 { CardRank::ThreeOfAKind } else if card_counts[0] == 2 && card_counts[1] == 2 { CardRank::TwoPair } else if card_counts[0] == 2 { CardRank::OnePair } else { CardRank::HighCard } } } } impl Card { fn parser(with_jokers: bool) -> impl FnMut(&str) -> IResult<&str, Self> { move |input: &str| { map( alt(( value(2, nom_char('2')), value(3, nom_char('3')), value(4, nom_char('4')), value(5, nom_char('5')), value(6, nom_char('6')), value(7, nom_char('7')), value(8, nom_char('8')), value(9, nom_char('9')), value(10, nom_char('T')), value(if with_jokers { 1 } else { 11 }, nom_char('J')), value(12, nom_char('Q')), value(13, nom_char('K')), value(14, nom_char('A')), )), Card, )(input) } } } impl std::cmp::Ord for CardHand { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.card_rank() .cmp(&other.card_rank()) .then(self.cards.cmp(&other.cards)) } } impl std::cmp::PartialOrd for CardHand { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } }