Skip to content

Instantly share code, notes, and snippets.

@koivunej
Created December 9, 2023 13:08
Show Gist options
  • Save koivunej/36e6a9172c799e60e0aa54f7a7ce6423 to your computer and use it in GitHub Desktop.
Save koivunej/36e6a9172c799e60e0aa54f7a7ce6423 to your computer and use it in GitHub Desktop.
aoc 2023 day 07
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