309 lines
7.2 KiB
Rust
309 lines
7.2 KiB
Rust
use std::cmp::Ordering;
|
|
use std::collections::HashMap;
|
|
use std::fmt::Debug;
|
|
use std::hash::Hash;
|
|
use HandType::*;
|
|
|
|
const FILE_STR: &str = include_str!("input.txt");
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
|
#[repr(u8)]
|
|
enum PartOne {
|
|
#[default]
|
|
Two = 0,
|
|
Three,
|
|
Four,
|
|
Five,
|
|
Six,
|
|
Seven,
|
|
Eight,
|
|
Nine,
|
|
Joker,
|
|
Jack,
|
|
Queen,
|
|
King,
|
|
Ace,
|
|
}
|
|
|
|
impl From<char> for PartOne {
|
|
fn from(value: char) -> Self {
|
|
use PartOne::*;
|
|
|
|
match value {
|
|
'2' => Two,
|
|
'3' => Three,
|
|
'4' => Four,
|
|
'5' => Five,
|
|
'6' => Six,
|
|
'7' => Seven,
|
|
'8' => Eight,
|
|
'9' => Nine,
|
|
'T' => Joker,
|
|
'J' => Jack,
|
|
'Q' => Queen,
|
|
'K' => King,
|
|
'A' => Ace,
|
|
_ => panic!("Invalid card character"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
|
#[repr(u8)]
|
|
enum PartTwo {
|
|
#[default]
|
|
Joker = 0,
|
|
Two,
|
|
Three,
|
|
Four,
|
|
Five,
|
|
Six,
|
|
Seven,
|
|
Eight,
|
|
Nine,
|
|
Jack,
|
|
Queen,
|
|
King,
|
|
Ace,
|
|
}
|
|
|
|
impl From<char> for PartTwo {
|
|
fn from(value: char) -> Self {
|
|
use PartTwo::*;
|
|
match value {
|
|
'J' => Joker,
|
|
'2' => Two,
|
|
'3' => Three,
|
|
'4' => Four,
|
|
'5' => Five,
|
|
'6' => Six,
|
|
'7' => Seven,
|
|
'8' => Eight,
|
|
'9' => Nine,
|
|
'T' => Jack,
|
|
'Q' => Queen,
|
|
'K' => King,
|
|
'A' => Ace,
|
|
_ => panic!("Invalid card character"),
|
|
}
|
|
}
|
|
}
|
|
trait CardType: Eq + Copy + PartialOrd + Ord + Debug + Hash + From<char> {}
|
|
impl CardType for PartOne {}
|
|
impl CardType for PartTwo {}
|
|
|
|
#[derive(Debug, Default, PartialEq, Eq)]
|
|
struct Hand<T> {
|
|
cards: [T; 5],
|
|
kind: HandType,
|
|
bet: usize,
|
|
}
|
|
|
|
impl<T: CardType> Hand<T> {
|
|
fn parse(line: &str) -> Self {
|
|
let mut parts = line.split_whitespace();
|
|
let raw_hand = parts.next().unwrap();
|
|
let raw_bet = parts.next().unwrap();
|
|
let cards: [T; 5] = raw_hand
|
|
.trim()
|
|
.chars()
|
|
.map(|ch| ch.into())
|
|
.collect::<Vec<T>>()
|
|
.try_into()
|
|
.unwrap();
|
|
let bet = raw_bet.trim().parse::<usize>().unwrap();
|
|
let kind = HandType::from(cards);
|
|
|
|
Hand { cards, kind, bet }
|
|
}
|
|
}
|
|
|
|
impl<T: CardType> PartialOrd for Hand<T> {
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
Some(self.cmp(other))
|
|
}
|
|
}
|
|
|
|
impl<T: CardType> Ord for Hand<T> {
|
|
/// Implement the sorting for each hand, first comparing the type of
|
|
/// hand, and then comparing the individual cards, if necessary
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
if self.kind != other.kind {
|
|
return self.kind.cmp(&other.kind);
|
|
}
|
|
|
|
for (i, card) in self.cards.into_iter().enumerate() {
|
|
if card != other.cards[i] {
|
|
return card.cmp(&other.cards[i]);
|
|
}
|
|
}
|
|
|
|
Ordering::Equal
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default, PartialOrd, Ord, PartialEq, Eq, Copy, Clone, Hash)]
|
|
#[repr(u8)]
|
|
enum HandType {
|
|
#[default]
|
|
HighCard = 0,
|
|
OnePair,
|
|
TwoPair,
|
|
ThreeOfAKind,
|
|
FullHouse,
|
|
FourOfAKind,
|
|
FiveOfAKind,
|
|
}
|
|
|
|
impl HandType {
|
|
fn from_cards(value: [PartTwo; 5]) -> Self {
|
|
let mut cards = Vec::from(value);
|
|
cards.sort_unstable();
|
|
|
|
let mut count_map: HashMap<PartTwo, usize> = HashMap::new();
|
|
cards.into_iter().for_each(|ct| {
|
|
match count_map.get(&ct) {
|
|
Some(count) => count_map.insert(ct, *count + 1),
|
|
None => count_map.insert(ct, 1),
|
|
};
|
|
});
|
|
let mut card_type_counts: Vec<usize> = count_map.clone().into_values().collect();
|
|
card_type_counts.sort_unstable();
|
|
|
|
let joker_count = if let Some(count) = count_map.get(&PartTwo::Joker) {
|
|
*count
|
|
} else {
|
|
0usize
|
|
};
|
|
|
|
let base_type = match card_type_counts[..] {
|
|
[5] => FiveOfAKind,
|
|
[1, 4] => FourOfAKind,
|
|
[2, 3] => FullHouse,
|
|
[1, 1, 3] => ThreeOfAKind,
|
|
[1, 2, 2] => TwoPair,
|
|
[1, 1, 1, 2] => OnePair,
|
|
_ => HighCard,
|
|
};
|
|
|
|
match (joker_count, base_type) {
|
|
(0, _) => base_type,
|
|
|
|
(_, FourOfAKind) => FiveOfAKind,
|
|
(_, FullHouse) => FiveOfAKind,
|
|
(_, ThreeOfAKind) => FourOfAKind,
|
|
|
|
(1, TwoPair) => FullHouse,
|
|
(_, TwoPair) => FourOfAKind,
|
|
(_, OnePair) => ThreeOfAKind,
|
|
|
|
(_, HighCard) => OnePair,
|
|
|
|
(_, _) => base_type,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: CardType> From<[T; 5]> for HandType {
|
|
fn from(value: [T; 5]) -> Self {
|
|
let mut cards = Vec::from(value);
|
|
cards.sort_unstable();
|
|
|
|
let mut count_map: HashMap<T, usize> = HashMap::new();
|
|
cards.into_iter().for_each(|ct| {
|
|
match count_map.get(&ct) {
|
|
Some(count) => count_map.insert(ct, *count + 1),
|
|
None => count_map.insert(ct, 1),
|
|
};
|
|
});
|
|
let mut card_type_counts: Vec<usize> = count_map.into_values().collect();
|
|
card_type_counts.sort_unstable();
|
|
|
|
match card_type_counts[..] {
|
|
[5] => FiveOfAKind,
|
|
[1, 4] => FourOfAKind,
|
|
[2, 3] => FullHouse,
|
|
[1, 1, 3] => ThreeOfAKind,
|
|
[1, 2, 2] => TwoPair,
|
|
[1, 1, 1, 2] => OnePair,
|
|
_ => HighCard,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
struct Game<T> {
|
|
hands: Vec<Hand<T>>,
|
|
}
|
|
|
|
impl<T: CardType> Game<T> {
|
|
fn parse(input: &str) -> Game<T> {
|
|
let hands = input
|
|
.split('\n')
|
|
.filter(|s| !s.is_empty())
|
|
.map(Hand::parse)
|
|
.collect::<Vec<Hand<T>>>();
|
|
|
|
Game { hands }
|
|
}
|
|
|
|
fn get_score_sum(&mut self) -> usize {
|
|
// Rank the hands
|
|
self.hands.sort_unstable();
|
|
|
|
self.hands.iter().enumerate().fold(0, |prev, curr| {
|
|
let (i, hand) = curr;
|
|
let rank = i + 1;
|
|
|
|
prev + (rank * hand.bet)
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Game<PartTwo> {
|
|
fn get_wild_score_sum(&mut self) -> usize {
|
|
self.hands
|
|
.iter_mut()
|
|
.for_each(|hand| hand.kind = HandType::from_cards(hand.cards));
|
|
|
|
self.get_score_sum()
|
|
}
|
|
}
|
|
|
|
fn part_one() {
|
|
let mut game: Game<PartOne> = Game::parse(FILE_STR);
|
|
let score = game.get_score_sum();
|
|
|
|
println!("Part 1: Sum of scores: {}", score);
|
|
}
|
|
|
|
fn part_two() {
|
|
let mut game: Game<PartTwo> = Game::parse(FILE_STR);
|
|
let score = game.get_wild_score_sum();
|
|
|
|
println!("Part 2: Sum of wildcard scores: {}", score);
|
|
}
|
|
|
|
fn main() {
|
|
part_one();
|
|
part_two();
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
const EXAMPLE_FILE_STR: &str = include_str!("example-input.txt");
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn get_score_sum() {
|
|
let mut game: Game<PartOne> = Game::parse(EXAMPLE_FILE_STR);
|
|
assert_eq!(6440, game.get_score_sum());
|
|
}
|
|
|
|
#[test]
|
|
fn get_wild_score_sum() {
|
|
let mut game: Game<PartTwo> = Game::parse(EXAMPLE_FILE_STR);
|
|
assert_eq!(5905, game.get_wild_score_sum());
|
|
}
|
|
}
|