advent-of-code/2023/src/bin/day07.rs
2023-12-07 12:49:37 +01:00

120 lines
3.5 KiB
Rust

#![feature(test)]
extern crate test;
use aoc2023::{boilerplate, common::*};
use itertools::Itertools;
const DAY: usize = 7;
type I = usize;
type Hand = [u8; 5];
type Parsed = Vec<(Hand, I)>;
const CARDS: [u8; 13] = [b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'T', b'J', b'Q', b'K', b'A'];
const CARDS_P2: [u8; 13] = [b'J', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'T', b'Q', b'K', b'A'];
enum Quality {
AllEqual = 1 << 30,
Quad = 1 << 29,
FullHouse = 1 << 28,
Triple = 1 << 27,
TwoPair = 1 << 26,
Pair = 1 << 25,
None = 1 << 24,
}
impl Quality {
// How adding 1 joker raises the quality of a hand.
fn upgrade(&self) -> Self {
match self {
Quality::AllEqual | Quality::Quad => Quality::AllEqual,
Quality::Triple | Quality::FullHouse => Quality::Quad,
Quality::TwoPair => Quality::FullHouse,
Quality::Pair => Quality::Triple,
Quality::None => Quality::Pair,
}
}
}
fn parse_input(raw: &str) -> Parsed {
raw.lines()
.map(|l| l.split_once(' ').unwrap())
.map(|(hand, points)| (<[_; 5]>::try_from(hand.as_bytes()).unwrap(), parse_num(points)))
.collect()
}
fn rate_hand(mut hand: Hand) -> Quality {
hand.sort();
let paired = hand
.into_iter()
.map(|c| (c, 1))
.coalesce(|(c1, n1), (c2, n2)| if c1 == c2 { Ok((c1, n1 + n2)) } else { Err(((c1, n1), (c2, n2))) })
.map(|(_, n)| n)
.collect_vec();
match paired.as_slice() {
[_] => Quality::AllEqual,
[4, 1] | [1, 4] => Quality::Quad,
[3, 2] | [2, 3] => Quality::FullHouse,
a @ [_, _, _] if a.contains(&3) => Quality::Triple,
[_, _, _] => Quality::TwoPair,
[_, _, _, _] => Quality::Pair,
_ => Quality::None,
}
}
fn tiebreaker(hand: &Hand, values: &[u8; 13]) -> usize {
let tiebreak_hand = hand.map(|b| values.iter().position(|&c| c == b).unwrap());
tiebreak_hand.iter().fold(0, |acc, n| (acc << 4) + n)
}
fn part1(hands: &Parsed) -> usize {
let mut rated_hands = hands
.iter()
.cloned()
.map(|(hand, points)| {
let tiebreak = tiebreaker(&hand, &CARDS);
(rate_hand(hand) as usize + tiebreak, points)
})
.collect_vec();
rated_hands.sort_unstable_by_key(|(v, _)| *v);
rated_hands.into_iter().zip(1..).map(|((_, points), position)| points * position).sum()
}
fn part2(hands: &Parsed) -> usize {
let mut rated_hands = hands
.iter()
.cloned()
.map(|(mut hand, points)| {
let tiebreak = tiebreaker(&hand, &CARDS_P2);
// Count how many jokers there are and insert nonsense data for them so they can’t produce any pairs.
let mut jokers = 0;
for c in hand.iter_mut().filter(|c| c == &&b'J') {
*c = jokers;
jokers += 1;
}
let mut rating = rate_hand(hand);
for _ in 0..jokers {
rating = rating.upgrade();
}
(rating as usize + tiebreak, points)
})
.collect_vec();
rated_hands.sort_unstable_by_key(|(v, _)| *v);
rated_hands.into_iter().zip(1..).map(|((_, points), position)| points * position).sum()
}
boilerplate! {
TEST_INPUT == "\
32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483",
tests: {
part1: { TEST_INPUT => 6440 },
part2: { TEST_INPUT => 5905 },
},
bench1 == 248812215,
bench2 == 250057090,
bench_parse: Vec::len => 1000,
}