//! Day 7: Camel Cards use std::{ cmp::Ordering, collections::{BTreeSet, HashMap}, }; use itertools::Itertools; pub const DAY: u8 = 7; pub const TITLE: &str = "Camel Cards"; #[derive(Debug, PartialEq, Eq)] struct Hand(String); #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] enum HandType { HighCard, OnePair, TwoPair, ThreeOfAKind, FullHouse, FourOfAKind, FiveOfAKind, } fn card_value(c: char, v2: bool) -> u8 { if let Some(d) = c.to_digit(10) { d as u8 } else { match c { 'T' => 10, 'J' => { if v2 { 1 } else { 11 } } 'Q' => 12, 'K' => 13, 'A' => 14, _ => panic!("invalid card `{c}`"), } } } impl Hand { fn hand_type(&self, v2: bool) -> HandType { let mut card_counts = self.0.chars().counts(); let mut joker_count = if v2 { card_counts.remove(&'J').unwrap_or_default() } else { 0 }; let card_counts_sorted = card_counts.values().cloned().sorted().collect_vec(); let mut cc_highest = card_counts_sorted.last().cloned().unwrap_or_default(); let jokers_used = joker_count.min(5 - cc_highest); cc_highest += jokers_used; joker_count -= jokers_used; let cc_second_highest = card_counts_sorted .len() .checked_sub(2) .and_then(|i| card_counts_sorted.get(i)) .cloned() .unwrap_or_default() + joker_count; match cc_highest { 5 => HandType::FiveOfAKind, 4 => HandType::FourOfAKind, 3 => match cc_second_highest { 2 => HandType::FullHouse, _ => HandType::ThreeOfAKind, }, 2 => match cc_second_highest { 2 => HandType::TwoPair, _ => HandType::OnePair, }, 1 => HandType::HighCard, _ => panic!("invalid hand `{}`", self.0), } } fn compare1(&self, other: &Self, v2: bool) -> Ordering { self.hand_type(v2).cmp(&other.hand_type(v2)).then_with(|| { self.0 .chars() .zip(other.0.chars()) .find_map(|(a, b)| { let r = card_value(a, v2).cmp(&card_value(b, v2)); if r.is_ne() { Some(r) } else { None } }) .unwrap_or(Ordering::Equal) }) } } fn parse(input: Vec) -> Vec<(Hand, u32)> { input .into_iter() .map(|l| { let (a, b) = l.split_once(' ').unwrap(); (Hand(a.to_owned()), b.parse().unwrap()) }) .collect() } fn solution(input: Vec, v2: bool) -> String { let parsed = parse(input); parsed .iter() .sorted_by(|(h1, _), (h2, _)| h1.compare1(h2, v2)) .enumerate() .map(|(rank, (_, bid))| (rank as u32 + 1) * bid) .sum::() .to_string() } pub fn part1(input: Vec) -> String { solution(input, false) } pub fn part2(input: Vec) -> String { solution(input, true) } #[cfg(test)] mod tests { use super::*; #[test] fn t_example1() { assert_eq!(part1(crate::read_example(DAY, 1)), "6440"); } #[test] fn t_part1() { assert_eq!(part1(crate::read_input(DAY)), "253933213"); } #[test] fn t_example2() { assert_eq!(part2(crate::read_example(DAY, 2)), "5905"); } #[test] fn t_part2() { assert_eq!(part2(crate::read_input(DAY)), "253473930"); } }