200 lines
5.9 KiB
Rust
200 lines
5.9 KiB
Rust
use std::{fmt::Display, cmp::Ordering};
|
|
|
|
use itertools::Itertools;
|
|
|
|
use crate::day_solver::DaySolver;
|
|
#[cfg(test)]
|
|
use crate::util::read_file;
|
|
|
|
const CARD_ORDER: [char; 13] = ['A', 'K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2'];
|
|
|
|
pub struct Day7 {
|
|
hands: Vec<Hand>
|
|
}
|
|
|
|
pub struct Hand {
|
|
cards: [char; 5],
|
|
bid: usize
|
|
}
|
|
|
|
impl Display for Hand {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{} {}", self.cards.iter().collect::<String>(), self.bid)
|
|
}
|
|
}
|
|
|
|
impl Hand {
|
|
fn parse(input: &str) -> Self {
|
|
let (cards_str, bid_str) = input.split_once(" ").unwrap();
|
|
return Hand {
|
|
cards: cards_str.chars().collect::<Vec<char>>().try_into().unwrap(),
|
|
bid: bid_str.parse().unwrap()
|
|
}
|
|
}
|
|
|
|
fn calc_type(&self) -> u8 {
|
|
let mut found_three_of_kind = false;
|
|
let mut found_pair = false;
|
|
for unique_card in self.cards.iter().unique() {
|
|
let card_appearances = self.cards.iter().filter(|c| c == &unique_card).count();
|
|
if card_appearances == 5 {
|
|
return FIVE_OF_A_KIND;
|
|
} else if card_appearances == 4 {
|
|
return FOUR_OF_A_KIND;
|
|
} else if card_appearances == 3 {
|
|
found_three_of_kind = true;
|
|
} else if card_appearances == 2 {
|
|
if found_pair {
|
|
return TWO_PAIR
|
|
} else {
|
|
found_pair = true
|
|
}
|
|
}
|
|
}
|
|
if found_three_of_kind && found_pair {
|
|
return FULL_HOUSE;
|
|
} else if found_three_of_kind {
|
|
return THREE_OF_A_KIND;
|
|
} else if found_pair {
|
|
return ONE_PAIR;
|
|
}
|
|
return HIGH_CARD;
|
|
}
|
|
|
|
// Calculates the maximum type, given that "J" is a joker
|
|
fn calc_max_type(&self) -> u8 {
|
|
if !self.cards.contains(&'J') {
|
|
return self.calc_type();
|
|
}
|
|
|
|
let mut max_type = self.calc_type();
|
|
if max_type == FIVE_OF_A_KIND { return max_type };
|
|
|
|
// It only makes sense to try to map jokers to any of the other cards we have
|
|
let owned_cards: Vec<&char> = self.cards.iter().filter(|c| c != &&'J').unique().collect();
|
|
for i in 0..5 {
|
|
if self.cards[i] == 'J' {
|
|
for c in &owned_cards {
|
|
let mut new_cards = self.cards.clone();
|
|
new_cards[i] = *c.to_owned();
|
|
let new_hand = Hand { cards: new_cards, bid: 0};
|
|
let j_type = new_hand.calc_max_type();
|
|
// println!("Trying hand {} as {}, got type {}", self, new_hand, j_type);
|
|
if j_type > max_type {
|
|
max_type = j_type;
|
|
if max_type == FIVE_OF_A_KIND { return max_type }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return max_type;
|
|
}
|
|
}
|
|
|
|
const FIVE_OF_A_KIND: u8 = 64;
|
|
const FOUR_OF_A_KIND: u8 = 32;
|
|
const FULL_HOUSE: u8 = 16;
|
|
const THREE_OF_A_KIND: u8 = 8;
|
|
const TWO_PAIR: u8 = 4;
|
|
const ONE_PAIR: u8 = 2;
|
|
const HIGH_CARD: u8 = 0;
|
|
|
|
impl Day7 {
|
|
|
|
pub fn create(input: String) -> Self {
|
|
// let lines = input.lines();
|
|
|
|
// Put the input into the day struct
|
|
return Day7 {
|
|
hands: input.lines().map(Hand::parse).collect()
|
|
}
|
|
}
|
|
}
|
|
|
|
fn card_rank(card: &char) -> usize {
|
|
for i in 0..CARD_ORDER.len() {
|
|
if &CARD_ORDER[i] == card {
|
|
return i;
|
|
}
|
|
}
|
|
CARD_ORDER.len()
|
|
}
|
|
|
|
fn compare_card(a: &char, b: &char) -> Ordering {
|
|
return card_rank(b).partial_cmp(&card_rank(a)).unwrap();
|
|
}
|
|
|
|
fn compare_card_pt2(a: &char, b: &char) -> Ordering {
|
|
if a == &'J' && b == &'J' { return Ordering::Equal }
|
|
else if a == &'J' { return Ordering::Less }
|
|
else if b == &'J' { return Ordering::Greater }
|
|
else {
|
|
return card_rank(b).partial_cmp(&card_rank(a)).unwrap();
|
|
}
|
|
}
|
|
|
|
impl DaySolver for Day7 {
|
|
|
|
|
|
fn solve_part1(&mut self) -> String {
|
|
let mut hands_and_types: Vec<(&Hand, u8)> = self.hands.iter()
|
|
.map(|h| (h, h.calc_type()))
|
|
.collect();
|
|
hands_and_types.sort_by(|a, b| {
|
|
if a.1 != b.1 {
|
|
return a.1.partial_cmp(&b.1).unwrap();
|
|
} else {
|
|
for i in 0usize..5 {
|
|
let card_comp = compare_card(&a.0.cards[i], &b.0.cards[i]);
|
|
if card_comp.is_ne() {
|
|
return card_comp;
|
|
}
|
|
}
|
|
return Ordering::Equal;
|
|
}
|
|
});
|
|
return hands_and_types.iter().map(|(h,_)|h).enumerate()
|
|
.map(|(r, h)| (r + 1) * h.bid)
|
|
.sum::<usize>()
|
|
.to_string();
|
|
}
|
|
|
|
fn solve_part2(&mut self) -> String {
|
|
let mut hands_and_max_types: Vec<(&Hand, u8)> = self.hands.iter()
|
|
.map(|h| (h, h.calc_max_type()))
|
|
.collect();
|
|
hands_and_max_types.sort_by(|a, b| {
|
|
if a.1 != b.1 {
|
|
return a.1.partial_cmp(&b.1).unwrap();
|
|
} else {
|
|
for i in 0..5 {
|
|
let card_comp = compare_card_pt2(&a.0.cards[i], &b.0.cards[i]);
|
|
if card_comp.is_ne() {
|
|
return card_comp;
|
|
}
|
|
}
|
|
return Ordering::Equal;
|
|
}
|
|
});
|
|
// for (hand, max_type) in &hands_and_max_types {
|
|
// println!("Hand {} with type {}", hand, max_type);
|
|
// }
|
|
return hands_and_max_types.iter().map(|(h,_)|h).enumerate()
|
|
.map(|(r, h)| (r + 1) * h.bid)
|
|
.sum::<usize>()
|
|
.to_string();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_part1() {
|
|
let mut day = Day7::create(read_file("input/day07_example.txt"));
|
|
assert_eq!("6440", day.solve_part1());
|
|
}
|
|
|
|
#[test]
|
|
fn test_part2() {
|
|
let mut day = Day7::create(read_file("input/day07_example.txt"));
|
|
assert_eq!("5905", day.solve_part2());
|
|
}
|