Created
December 9, 2023 13:08
-
-
Save koivunej/36e6a9172c799e60e0aa54f7a7ce6423 to your computer and use it in GitHub Desktop.
aoc 2023 day 07
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::io::BufRead; | |
fn main() { | |
let mut parsed = parse(std::io::stdin().lock()); | |
let phase1 = total_winnings(&mut parsed); | |
assert_eq!(phase1, 250232501); | |
let mut parsed = parsed | |
.into_iter() | |
.map(|(hand, bid)| (SecondHand(hand), bid)) | |
.collect::<Vec<_>>(); | |
let phase2 = total_winnings(&mut parsed); | |
assert_ne!(phase2, 249677939, "high"); | |
assert_eq!(phase2, 249138943); | |
} | |
#[derive(Clone, Copy, PartialOrd, PartialEq, Eq, Ord, Default)] | |
struct Card(u8); | |
impl TryFrom<u8> for Card { | |
type Error = u8; | |
fn try_from(value: u8) -> Result<Self, Self::Error> { | |
if value < 13 { | |
Ok(Card(value)) | |
} else { | |
Err(value) | |
} | |
} | |
} | |
impl TryFrom<char> for Card { | |
type Error = char; | |
fn try_from(value: char) -> Result<Self, Self::Error> { | |
let conv = match value { | |
'2'..='9' => value as u8 - b'2', | |
'T' => 8, | |
'J' => 9, | |
'Q' => 10, | |
'K' => 11, | |
'A' => 12, | |
_ => return Err(value), | |
}; | |
Card::try_from(conv).map_err(|_| value) | |
} | |
} | |
impl std::fmt::Debug for Card { | |
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
std::fmt::Display::fmt(&self, f) | |
} | |
} | |
impl std::fmt::Display for Card { | |
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
// 0 => 2 | |
// 1 => 3 | |
// 2 => 4 | |
// 3 => 5 | |
// 4 => 6 | |
// 5 => 7 | |
// 6 => 8 | |
// 7 => 9 | |
// 8 => T | |
// 9 => J | |
// 10 => Q | |
// 11 => K | |
// 12 => A | |
let offset = self.0 + 2; | |
if offset <= 9 { | |
write!(f, "{}", offset) | |
} else { | |
let ch = match offset { | |
10 => 'T', | |
11 => 'J', | |
12 => 'Q', | |
13 => 'K', | |
14 => 'A', | |
_ => unreachable!("too large value: {offset}"), | |
}; | |
write!(f, "{ch}") | |
} | |
} | |
} | |
#[derive(Clone, Copy, PartialEq, Eq, Ord, Default)] | |
struct Hand([Card; 5]); | |
impl std::fmt::Display for Hand { | |
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
self.0.iter().try_for_each(|card| write!(f, "{card}")) | |
} | |
} | |
impl PartialOrd for Hand { | |
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { | |
let our_kind = self.identify(); | |
let their_kind = other.identify(); | |
print!("{self} ({our_kind:?}) vs. {other} ({their_kind:?}) => "); | |
let order = our_kind.cmp(&their_kind); | |
print!("{order:?} "); | |
let order = order.then_with(|| { | |
let orderings = self | |
.0 | |
.iter() | |
.zip(other.0.iter()) | |
.map(|(a, b)| a.cmp(b)) | |
.enumerate(); | |
for (i, ordering) in orderings { | |
if ordering != std::cmp::Ordering::Equal { | |
print!("{ordering:?} (determined on {}th card)", i + 1); | |
return ordering; | |
} | |
} | |
std::cmp::Ordering::Equal | |
}); | |
println!(); | |
Some(order) | |
} | |
} | |
impl Hand { | |
fn identify(&self) -> HandKind { | |
let mut card_counts = [(0, Card::default()); 13]; | |
card_counts | |
.iter_mut() | |
.enumerate() | |
.for_each(|(i, (_count, slot))| { | |
*slot = Card::try_from(u8::try_from(i).unwrap()).unwrap() | |
}); | |
for card in self.0 { | |
let count = &mut card_counts[card.0 as usize]; | |
*(&mut count.0) += 1; | |
} | |
card_counts.sort_unstable_by(|a, b| { | |
(a.0) | |
.cmp(&b.0) | |
.reverse() | |
.then_with(|| a.1.cmp(&b.1).reverse()) | |
}); | |
match (card_counts[0], card_counts[1]) { | |
((5, _card), _) => HandKind::FiveOfKind, | |
((4, _card), _) => HandKind::FourOfKind, | |
((3, _three), (2, _two)) => HandKind::FullHouse, | |
((3, _card), _) => HandKind::ThreeOfKind, | |
((2, _first), (2, _second)) => HandKind::TwoPair, | |
((2, _card), _) => HandKind::OnePair, | |
((1, _card), _) => HandKind::HighCard, | |
t => unreachable!("hand has 5 cards, unexpected: {t:?} out of {self}"), | |
} | |
} | |
} | |
impl<'a> TryFrom<&'a str> for Hand { | |
type Error = InvalidHand; | |
fn try_from(value: &'a str) -> Result<Self, Self::Error> { | |
use InvalidHand::*; | |
if value.len() == 5 { | |
let mut cards = [Card::default(); 5]; | |
value | |
.chars() | |
.into_iter() | |
.map(|ch| Card::try_from(ch).map_err(InvalidKey)) | |
.zip(cards.iter_mut()) | |
.try_for_each(|(card, slot)| { | |
*slot = card?; | |
Ok(()) | |
})?; | |
Ok(Hand(cards)) | |
} else { | |
Err(InvalidLength) | |
} | |
} | |
} | |
#[derive(Debug)] | |
enum InvalidHand { | |
InvalidLength, | |
InvalidKey(char), | |
} | |
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] | |
enum HandKind { | |
HighCard, | |
OnePair, | |
TwoPair, | |
ThreeOfKind, | |
FullHouse, | |
FourOfKind, | |
FiveOfKind, | |
} | |
#[cfg(test)] | |
macro_rules! hand { | |
($s:literal) => {{ | |
Hand::try_from($s).unwrap() | |
}}; | |
} | |
#[test] | |
fn hand_ordering() { | |
assert!(hand!("AA8AA") < hand!("AAAAA")); | |
assert!(hand!("23332") < hand!("AA8AA")); | |
assert!(hand!("TTT98") < hand!("23332")); | |
assert!(hand!("23432") < hand!("TTT98")); | |
assert!(hand!("A32A4") < hand!("23432")); | |
assert!(hand!("23456") < hand!("A32A4")); | |
} | |
#[test] | |
fn same_kind_hand_ordering() { | |
assert!(hand!("KKKKK") < hand!("AAAAA")); | |
assert!(hand!("KK8KK") < hand!("AA8AA")); | |
assert!(hand!("2AAAA") < hand!("33332")); | |
assert!(hand!("23322") < hand!("34433")); | |
assert!(hand!("77788") < hand!("77888")); | |
assert!(hand!("24433") < hand!("24455")); | |
assert!(hand!("24542") < hand!("34543")); | |
assert!(hand!("23456") < hand!("34567")); | |
} | |
#[test] | |
fn example() { | |
let input = "32T3K 765 | |
T55J5 684 | |
KK677 28 | |
KTJJT 220 | |
QQQJA 483"; | |
let mut hands = parse_str(&input); | |
assert_eq!(total_winnings(&mut hands), 6440); | |
let mut hands = hands | |
.into_iter() | |
.map(|(x, y)| (SecondHand(x), y)) | |
.collect::<Vec<_>>(); | |
assert_eq!(total_winnings(&mut hands), 5905); | |
} | |
fn total_winnings<T: Ord + Copy>(hand_bids: &mut [(T, u64)]) -> u64 { | |
hand_bids.sort_unstable_by_key(|x| x.0); | |
hand_bids | |
.iter() | |
.enumerate() | |
.map(|(i, (_, bid))| (i as u64 + 1) * bid) | |
.sum() | |
} | |
#[cfg(test)] | |
fn parse_str(s: &str) -> Vec<(Hand, u64)> { | |
parse(std::io::Cursor::new(s)) | |
} | |
fn parse(mut input: impl BufRead) -> Vec<(Hand, u64)> { | |
let mut out = Vec::new(); | |
let mut s = String::new(); | |
let mut lineno = 0; | |
loop { | |
s.clear(); | |
let read = input.read_line(&mut s).unwrap(); | |
if read == 0 { | |
break; | |
} | |
lineno += 1; | |
let line = s.trim(); | |
let (hand, bid) = line.split_once(' ').unwrap(); | |
let hand = Hand::try_from(hand) | |
.map_err(|e| InvalidLine(lineno, e)) | |
.unwrap(); | |
let bid = bid.parse::<u64>().unwrap(); | |
out.push((hand, bid)); | |
} | |
out | |
} | |
#[derive(Debug)] | |
struct InvalidLine<E>(usize, E); | |
#[derive(Clone, Copy, PartialEq, Eq, Ord)] | |
struct SecondHand(Hand); | |
impl std::fmt::Display for SecondHand { | |
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
self.0.fmt(f) | |
} | |
} | |
impl PartialOrd for SecondHand { | |
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { | |
let our_kind = self.identify(); | |
let their_kind = other.identify(); | |
print!("{self} ({our_kind:?}) vs. {other} ({their_kind:?}) => "); | |
let order = our_kind.cmp(&their_kind); | |
print!("{order:?} "); | |
let order = order.then_with(|| { | |
fn permute(card: &Card) -> u8 { | |
let joker = Card::try_from('J').unwrap(); | |
match card.0 { | |
x if x == joker.0 => 0, | |
x => x + 1, | |
} | |
} | |
let orderings = self | |
.0 | |
.0 | |
.iter() | |
.zip(other.0 .0.iter()) | |
.map(|(a, b)| { | |
let a = permute(a); | |
let b = permute(b); | |
a.cmp(&b) | |
}) | |
.enumerate(); | |
for (i, ordering) in orderings { | |
if ordering != std::cmp::Ordering::Equal { | |
print!("{ordering:?} (determined on {}th card)", i + 1); | |
return ordering; | |
} | |
} | |
std::cmp::Ordering::Equal | |
}); | |
println!(); | |
Some(order) | |
} | |
} | |
impl SecondHand { | |
fn identify(&self) -> HandKind { | |
let mut card_counts = [(0, Card::default()); 13]; | |
card_counts | |
.iter_mut() | |
.enumerate() | |
.for_each(|(i, (_count, slot))| { | |
*slot = Card::try_from(u8::try_from(i).unwrap()).unwrap() | |
}); | |
for card in self.0 .0 { | |
let count = &mut card_counts[card.0 as usize]; | |
*(&mut count.0) += 1; | |
} | |
card_counts.sort_unstable_by(|a, b| { | |
(a.0) | |
.cmp(&b.0) | |
.reverse() | |
.then_with(|| a.1.cmp(&b.1).reverse()) | |
}); | |
let joker = Card::try_from('J').unwrap(); | |
let jokers_at = card_counts.iter().position(|x| x.1 == joker).unwrap(); | |
let mut eval = match jokers_at { | |
0 => (card_counts[1], card_counts[2]), | |
1 => (card_counts[0], card_counts[2]), | |
_ => (card_counts[0], card_counts[1]), | |
}; | |
let jokers = card_counts[jokers_at].0; | |
print!("eval_before: {eval:?} "); | |
eval.0 .0 = eval.0 .0 + jokers; | |
if eval.0 .0 > 5 { | |
// if there are more jokers, it does not matter | |
eval.1 .0 = (eval.1 .0 + eval.0 .0 - 5).min(5); | |
eval.0 .0 = 5; | |
} | |
print!("eval_after: {eval:?} with {} jokers ", jokers); | |
let res = match eval { | |
((5, _card), _) => HandKind::FiveOfKind, | |
((4, _card), _) => HandKind::FourOfKind, | |
((3, _three), (2, _two)) => HandKind::FullHouse, | |
((3, _card), _) => HandKind::ThreeOfKind, | |
((2, _first), (2, _second)) => HandKind::TwoPair, | |
((2, _card), _) => HandKind::OnePair, | |
((1, _card), _) => HandKind::HighCard, | |
t => unreachable!("hand has 5 cards, unexpected: {t:?} out of {self}"), | |
}; | |
println!("gives {res:?}"); | |
res | |
} | |
} | |
#[cfg(test)] | |
macro_rules! shand { | |
($s:literal) => {{ | |
SecondHand(hand!($s)) | |
}}; | |
} | |
#[test] | |
fn second_hand_orderings() { | |
assert!(shand!("JJJJJ") < shand!("AAAAA")); | |
assert!(shand!("QQQQ2") < shand!("JJJJJ")); | |
assert!(shand!("JKKK2") < shand!("QQQQ2")); | |
assert!(shand!("7788J") < shand!("7778J")); | |
assert!(shand!("32T3K") < shand!("T55J5")); | |
assert!(shand!("32T3K") < shand!("KK677")); | |
assert!(shand!("T55J5") < shand!("QQQJA")); | |
assert!(shand!("QQQJA") < shand!("KTJJT")); | |
assert!(shand!("J2345") < shand!("52345")); | |
assert!(shand!("JJ345") < shand!("55345")); | |
assert!(shand!("JJJ45") < shand!("55545")); | |
assert!(shand!("JJJJ5") < shand!("55555")); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment