adventofcode23/src/day7.rs

158 lines
3.8 KiB
Rust

//! 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<String>) -> 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<String>, 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::<u32>()
.to_string()
}
pub fn part1(input: Vec<String>) -> String {
solution(input, false)
}
pub fn part2(input: Vec<String>) -> 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");
}
}