diff --git a/2023/src/day07.rs b/2023/src/day07.rs new file mode 100644 index 0000000..e0689a0 --- /dev/null +++ b/2023/src/day07.rs @@ -0,0 +1,360 @@ +use aoc_runner_derive::{aoc, aoc_generator}; +use std::cmp::Ordering; +use std::collections::{BTreeMap, HashSet}; +use std::str::FromStr; + +#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)] +enum Card { + A, + K, + Q, + J, + T, + C9, + C8, + C7, + C6, + C5, + C4, + C3, + C2, +} + +#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)] +enum Part2Card { + A, + K, + Q, + T, + C9, + C8, + C7, + C6, + C5, + C4, + C3, + C2, + J, +} + +fn card_to_part2(input: &Card) -> Part2Card { + match input { + Card::A => Part2Card::A, + Card::K => Part2Card::K, + Card::Q => Part2Card::Q, + Card::T => Part2Card::T, + Card::C9 => Part2Card::C9, + Card::C8 => Part2Card::C8, + Card::C7 => Part2Card::C7, + Card::C6 => Part2Card::C6, + Card::C5 => Part2Card::C5, + Card::C4 => Part2Card::C4, + Card::C3 => Part2Card::C3, + Card::C2 => Part2Card::C2, + Card::J => Part2Card::J, + } +} + +impl FromStr for Card { + type Err = (); + fn from_str(input: &str) -> Result { + match input { + "A" => Ok(Card::A), + "K" => Ok(Card::K), + "Q" => Ok(Card::Q), + "J" => Ok(Card::J), + "T" => Ok(Card::T), + "9" => Ok(Card::C9), + "8" => Ok(Card::C8), + "7" => Ok(Card::C7), + "6" => Ok(Card::C6), + "5" => Ok(Card::C5), + "4" => Ok(Card::C4), + "3" => Ok(Card::C3), + "2" => Ok(Card::C2), + _ => Err(()), + } + } +} + +#[derive(Debug, Clone, Eq, Ord, PartialEq, PartialOrd)] +enum HandKinds { + FiveAlike, + FourAlike, + FullHouse, + ThreeAlike, + TwoPair, + OnePair, + HighCard, +} + +#[derive(Debug, Clone)] +struct Hand { + bid: u64, + cards: [Card; 5], + hand_kind: HandKinds, +} + +impl PartialEq for Hand { + fn eq(&self, other: &Self) -> bool { + self.hand_kind == other.hand_kind && self.cards == other.cards + } +} + +impl Eq for Hand {} + +impl PartialOrd for Hand { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Hand { + fn cmp(&self, other: &Self) -> Ordering { + if self.hand_kind == other.hand_kind { + if self.cards[0] != other.cards[0] { + self.cards[0].cmp(&other.cards[0]) + } else if self.cards[1] != other.cards[1] { + self.cards[1].cmp(&other.cards[1]) + } else if self.cards[2] != other.cards[2] { + self.cards[2].cmp(&other.cards[2]) + } else if self.cards[3] != other.cards[3] { + self.cards[3].cmp(&other.cards[3]) + } else if self.cards[4] != other.cards[4] { + self.cards[4].cmp(&other.cards[4]) + } else { + panic!( + "could not sort hand {:?} with {:?}", + self.cards, other.cards + ) + } + } else { + self.hand_kind.cmp(&other.hand_kind) + } + } +} + +impl PartialEq for Part2Hand { + fn eq(&self, other: &Self) -> bool { + self.hand_kind == other.hand_kind && self.cards == other.cards + } +} + +impl Eq for Part2Hand {} + +impl PartialOrd for Part2Hand { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Part2Hand { + fn cmp(&self, other: &Self) -> Ordering { + if self.hand_kind == other.hand_kind { + if self.cards[0] != other.cards[0] { + self.cards[0].cmp(&other.cards[0]) + } else if self.cards[1] != other.cards[1] { + self.cards[1].cmp(&other.cards[1]) + } else if self.cards[2] != other.cards[2] { + self.cards[2].cmp(&other.cards[2]) + } else if self.cards[3] != other.cards[3] { + self.cards[3].cmp(&other.cards[3]) + } else if self.cards[4] != other.cards[4] { + self.cards[4].cmp(&other.cards[4]) + } else { + panic!( + "could not sort hand {:?} with {:?}", + self.cards, other.cards + ) + } + } else { + self.hand_kind.cmp(&other.hand_kind) + } + } +} + +fn get_hand_type(cards: &[T; 5]) -> HandKinds +where + T: std::fmt::Debug + Eq + PartialEq + PartialOrd + std::hash::Hash, +{ + let mut distinct_cards: HashSet<&T> = HashSet::new(); + for c in cards.iter() { + distinct_cards.insert(c); + } + match distinct_cards.len() { + 1 => HandKinds::FiveAlike, + 2 => { + let tcount = cards + .iter() + .fold(0, |acc, c| if *c == cards[0] { acc + 1 } else { acc }); + if tcount == 4 || tcount == 1 { + HandKinds::FourAlike + } else { + HandKinds::FullHouse + } + } + 3 => { + let mut counts = distinct_cards + .iter() + .map(|card| { + cards + .iter() + .fold(0, |acc, c| if c == *card { acc + 1 } else { acc }) + }) + .collect::>(); + counts.sort(); + if counts == [1, 1, 3] { + HandKinds::ThreeAlike + } else { + HandKinds::TwoPair + } + } + 4 => HandKinds::OnePair, + 5 => HandKinds::HighCard, + _ => panic!( + "expected to find size 1-5, but instead got {} from line {:?}", + distinct_cards.len(), + cards + ), + } +} + +#[derive(Debug)] +struct Part2Hand { + bid: u64, + cards: [Part2Card; 5], + hand_kind: HandKinds, +} + +#[aoc_generator(day7)] +fn parse(input: &str) -> Vec { + input + .lines() + .filter_map(|line| { + if !line.is_empty() { + let parts = line.split(" ").collect::>(); + let cards = TryInto::<[Card; 5]>::try_into( + parts[0] + .split("") + .filter_map(|c| { + if c.is_empty() { + None + } else { + Some(Card::from_str(c).unwrap()) + } + }) + .collect::>(), + ) + .unwrap_or_else(|v: Vec| { + panic!("Expected a Vec of length 5 but it was {}", v.len()) + }); + let bid = parts[1].parse::().unwrap(); + let hand_kind = get_hand_type(&cards); + Some(Hand { + bid, + cards, + hand_kind, + }) + } else { + None + } + }) + .collect() +} + +#[aoc(day7, part1)] +fn part1(input: &[Hand]) -> u64 { + let mut sum: u64 = 0; + let mut hands = input.iter().map(|h| h.clone()).collect::>(); + hands.sort(); + for (index, hand) in hands.iter().rev().enumerate() { + sum += hand.bid * (index as u64 + 1); + } + sum +} + +#[aoc(day7, part2)] +fn part2(input: &[Hand]) -> u64 { + let mut sum: u64 = 0; + let mut hands = input + .iter() + .map(|h| { + let bid = h.bid; + let p2cards = h + .cards + .iter() + .map(|c| card_to_part2(c)) + .collect::>(); + let mut counts: BTreeMap<&Part2Card, u32> = BTreeMap::new(); + p2cards.iter().for_each(|c| { + if !counts.contains_key(c) { + counts.insert(c, 0); + } + let ccount = counts.get_mut(c).unwrap(); + *ccount = *ccount + 1; + }); + + let mut highest_count = 0; + let mut highest_card = &Part2Card::J; + for (card, count) in counts.iter() { + if **card != Part2Card::J { + if *count > highest_count { + highest_count = *count; + highest_card = card; + } else if *count == highest_count && *card > highest_card { + highest_card = card; + } + } + } + let newhand = TryInto::<[Part2Card; 5]>::try_into( + p2cards + .iter() + .map(|c| { + if *c == Part2Card::J { + *highest_card + } else { + *c + } + }) + .collect::>(), + ) + .unwrap_or_else(|v: Vec| { + panic!("Expected a Vec of length 5 but it was {}", v.len()) + }); + let hand_kind = get_hand_type(&newhand); + let cards = + TryInto::<[Part2Card; 5]>::try_into(p2cards).unwrap_or_else(|v: Vec| { + panic!("Expected a Vec of length 5 but it was {}", v.len()) + }); + Part2Hand { + bid, + cards, + hand_kind, + } + }) + .collect::>(); + hands.sort(); + for (index, hand) in hands.iter().rev().enumerate() { + sum += hand.bid * (index as u64 + 1); + } + sum +} + +#[cfg(test)] +mod tests { + use super::*; + + const SAMPLE_DATA: &'static str = r#"32T3K 765 +T55J5 684 +KK677 28 +KTJJT 220 +QQQJA 483 +"#; + + #[test] + fn sample_data() { + let cards = parse(&SAMPLE_DATA); + assert_eq!(part1(&cards), 6440); + assert_eq!(part2(&cards), 5905); + } +} diff --git a/2023/src/lib.rs b/2023/src/lib.rs index 4d0b2b6..6bac528 100644 --- a/2023/src/lib.rs +++ b/2023/src/lib.rs @@ -6,5 +6,6 @@ mod day03; mod day04; mod day05; mod day06; +mod day07; aoc_lib! { year = 2023 }