Solved part 2 of day 7 partially

Example works
but not the real puzzle input
This commit is contained in:
BoolPurist 2024-08-23 11:29:22 +02:00
parent dad77e85c0
commit 480bc48d79
7 changed files with 341 additions and 113 deletions

View file

@ -0,0 +1,5 @@
32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483

View file

@ -10,7 +10,7 @@ pub fn create_solutions() -> Vec<Vec<fn(&str) -> String>> {
vec![not_implemented_yet, not_implemented_yet], vec![not_implemented_yet, not_implemented_yet],
vec![day5::solve_task_1, day5::solve_task_2], vec![day5::solve_task_1, day5::solve_task_2],
vec![day6::solve_task_1, day6::solve_task_2], vec![day6::solve_task_1, day6::solve_task_2],
vec![day7::solve_task_1, not_implemented_yet], vec![day7::solve_task_1, day7::solve_task_2],
vec![not_implemented_yet, not_implemented_yet], vec![not_implemented_yet, not_implemented_yet],
vec![not_implemented_yet, not_implemented_yet], vec![not_implemented_yet, not_implemented_yet],
vec![not_implemented_yet, not_implemented_yet], vec![not_implemented_yet, not_implemented_yet],

View file

@ -1,4 +1,4 @@
use categorized_hand::CategorizedHand; use categorized_hand::{CategorizedHand, JokerOrdered};
mod categorized_hand; mod categorized_hand;
mod dealt_hand; mod dealt_hand;
@ -17,6 +17,26 @@ pub fn solve_task_1(input: &str) -> String {
total_winning.to_string() total_winning.to_string()
} }
pub fn solve_task_2(input: &str) -> String {
const JOKER: char = 'J';
let parsed = parsing::parse_input(input);
let sorted_and_categorized: Vec<CategorizedHand> = {
let mut categorized: Vec<JokerOrdered> = parsed
.into_iter()
.map(|not_categorized| (not_categorized, JOKER).into())
.map(JokerOrdered::new)
.collect();
categorized.sort();
categorized
.into_iter()
.map(|to_unwrap| to_unwrap.into())
.collect()
};
let total_winning = calc_total_winning(&sorted_and_categorized);
total_winning.to_string()
}
fn calc_total_winning(sorted: &[CategorizedHand]) -> usize { fn calc_total_winning(sorted: &[CategorizedHand]) -> usize {
sorted sorted
.into_iter() .into_iter()
@ -52,4 +72,11 @@ mod testing {
let expected = "6440"; let expected = "6440";
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }
#[test]
fn solve_example_according_to_task_2() {
let actual = super::solve_task_2(TEST_INPUT);
let expected = "5905";
assert_eq!(expected, actual);
}
} }

View file

@ -1,10 +1,9 @@
use std::{cmp::Reverse, collections::HashMap, usize}; use derive_more::derive::Into;
use log::warn;
use crate::solutions::day7::second_ordering; use crate::solutions::day7::second_ordering;
use std::usize;
use super::{dealt_hand::DealtHand, hand_kind::HandKind}; use super::{dealt_hand::DealtHand, hand_kind::HandKind, second_ordering::StrengthOfSymbols};
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct CategorizedHand { pub struct CategorizedHand {
@ -12,7 +11,36 @@ pub struct CategorizedHand {
rest: DealtHand, rest: DealtHand,
} }
#[derive(Debug, PartialEq, Eq, Into)]
pub struct JokerOrdered(pub CategorizedHand);
impl JokerOrdered {
pub fn new(value: CategorizedHand) -> Self {
Self(value)
}
}
impl Ord for JokerOrdered {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
hand_cmp(
&self.0,
&other.0,
&second_ordering::LABEL_ORDERING_WITH_JOKER,
)
}
}
impl PartialOrd for JokerOrdered {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl CategorizedHand { impl CategorizedHand {
#[cfg(test)]
pub fn new(kind: HandKind, rest: DealtHand) -> Self {
Self { kind, rest }
}
pub fn rest(&self) -> &DealtHand { pub fn rest(&self) -> &DealtHand {
&self.rest &self.rest
} }
@ -23,30 +51,42 @@ impl CategorizedHand {
impl Ord for CategorizedHand { impl Ord for CategorizedHand {
fn cmp(&self, other: &Self) -> std::cmp::Ordering { fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match self.kind.cmp(&other.kind) { hand_cmp(self, other, &second_ordering::LABEL_ORDERING)
}
}
fn hand_cmp(
left: &CategorizedHand,
right: &CategorizedHand,
label_ordering: &StrengthOfSymbols,
) -> std::cmp::Ordering {
match left.kind.cmp(&right.kind) {
std::cmp::Ordering::Equal => { std::cmp::Ordering::Equal => {
assert!( assert!(
self.kind == other.kind, left.kind == right.kind,
"If ordering is equal then the hand kind must be the same too\n\ "If ordering is equal then the hand kind must be the same too\n\
Self kind: ({:?}) and other kind: ({:?})", left kind: ({:?}) and right kind: ({:?})",
self.kind, left.kind,
other.kind right.kind
); );
second_ordering::compare_along_str(self.rest().hand(), other.rest().hand()) second_ordering::compare_along_str(
left.rest().hand(),
right.rest().hand(),
label_ordering,
)
} }
not_equal_ordering => { not_equal_ordering => {
assert!( assert!(
self.kind != other.kind, left.kind != right.kind,
"If ordering is not equal then the hand kind must not be the same either\n\ "If ordering is not equal then the hand kind must not be the same either\n\
Self kind: ({:?}) and other kind: ({:?})", left kind: ({:?}) and right kind: ({:?})",
self.kind, left.kind,
other.kind right.kind
); );
not_equal_ordering not_equal_ordering
} }
} }
} }
}
impl PartialOrd for CategorizedHand { impl PartialOrd for CategorizedHand {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
@ -56,59 +96,13 @@ impl PartialOrd for CategorizedHand {
impl From<DealtHand> for CategorizedHand { impl From<DealtHand> for CategorizedHand {
fn from(value: DealtHand) -> Self { fn from(value: DealtHand) -> Self {
fn calc_kind(values: &[usize]) -> Option<HandKind> { let kind = value.hand().into();
let max = values.first()?; CategorizedHand { kind, rest: value }
let kind = match max {
5 => HandKind::Five,
4 => HandKind::Four,
under_five_or_four => {
let second_after_max = values.get(1)?;
match (under_five_or_four, second_after_max) {
(3, 2) => HandKind::FullHouse,
(3, _) => HandKind::Three,
(2, 2) => HandKind::TwoPair,
(2, 1) => HandKind::OnePair,
_no_duplicates => HandKind::HighCard,
} }
} }
}; impl From<(DealtHand, char)> for CategorizedHand {
fn from((value, joker): (DealtHand, char)) -> Self {
Some(kind) let kind = (value.hand(), joker).into();
}
fn calc_duplicate_counted(value: &DealtHand) -> Vec<usize> {
const FIRST_ENCOUNTERED: usize = 1;
let mut count_of_same: HashMap<char, usize> = HashMap::with_capacity(10);
for next_char in value.hand().chars() {
count_of_same
.entry(next_char)
.and_modify(|count| *count = *count + 1)
.or_insert(FIRST_ENCOUNTERED);
}
{
let mut to_sort: Vec<usize> =
count_of_same.into_iter().map(|(_, count)| count).collect();
to_sort.sort_by_key(|&count| Reverse(count));
to_sort
}
}
let duplicated_counted = calc_duplicate_counted(&value);
let kind = calc_kind(&duplicated_counted).unwrap_or_else(|| {
warn!(
"Got hand ({:?}) with less than 2 cards",
&duplicated_counted
);
let default_value = HandKind::default();
warn!("Default kind ({:?}) is assumed", default_value);
default_value
});
CategorizedHand { kind, rest: value } CategorizedHand { kind, rest: value }
} }
} }
@ -117,29 +111,7 @@ impl From<DealtHand> for CategorizedHand {
mod testing { mod testing {
use std::cmp::Ordering; use std::cmp::Ordering;
use crate::solutions::day7::{ use crate::solutions::day7::{categorized_hand::CategorizedHand, dealt_hand::DealtHand};
categorized_hand::CategorizedHand, dealt_hand::DealtHand, hand_kind::HandKind,
};
#[test]
fn from_dealt_hand_to_categorized_hand() {
fn assert_case(given: DealtHand, expected: HandKind) {
let expected_hand = CategorizedHand {
kind: expected,
rest: given.clone(),
};
let actual = given.into();
assert_eq!(expected_hand, actual);
}
assert_case(DealtHand::new("AAAAA", 20), HandKind::Five);
assert_case(DealtHand::new("AA8AA", 20), HandKind::Four);
assert_case(DealtHand::new("23332", 20), HandKind::FullHouse);
assert_case(DealtHand::new("TTT98", 20), HandKind::Three);
assert_case(DealtHand::new("23456", 20), HandKind::HighCard);
assert_case(DealtHand::new("23432", 20), HandKind::TwoPair);
assert_case(DealtHand::new("A23A4", 20), HandKind::OnePair);
}
#[test] #[test]
fn compare_correctly_by_kind_and_second_ordering() { fn compare_correctly_by_kind_and_second_ordering() {

View file

@ -1,3 +1,9 @@
use std::{cmp::Reverse, collections::HashMap};
use log::warn;
use non_zero_count::NonZeroCount;
mod non_zero_count;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)]
pub enum HandKind { pub enum HandKind {
/// High card, where all cards' labels are distinct: 23456 /// High card, where all cards' labels are distinct: 23456
@ -16,3 +22,168 @@ pub enum HandKind {
/// Five of a kind, where all five cards have the same label: AAAAA /// Five of a kind, where all five cards have the same label: AAAAA
Five, Five,
} }
impl From<(&'_ str, char)> for HandKind {
fn from((characters, joker_char): (&'_ str, char)) -> Self {
fn get_joker_count(joker: char, symbols: &str) -> Option<NonZeroCount> {
let count = symbols.chars().filter(|&next| next == joker).count();
NonZeroCount::new(count)
}
fn use_jokers(
kind: HandKind,
how_many_jokers: NonZeroCount,
) -> (HandKind, Option<NonZeroCount>) {
match kind {
HandKind::HighCard => (HandKind::OnePair, how_many_jokers.decrement()),
HandKind::OnePair => (HandKind::Three, how_many_jokers.decrement()),
HandKind::TwoPair => (HandKind::FullHouse, how_many_jokers.decrement()),
HandKind::Three => (HandKind::Four, how_many_jokers.decrement()),
HandKind::FullHouse => (HandKind::Four, how_many_jokers.decrement()),
HandKind::Four | HandKind::Five => (HandKind::Five, Some(how_many_jokers)),
}
}
let duplicated_counted = calc_duplicate_counted(&characters, |next| next != joker_char);
let mut current_kind = choose_kind_from(&duplicated_counted);
let mut joker_count = get_joker_count(joker_char, characters);
while joker_count.is_some() && current_kind != HandKind::Five {
let (new_kind, new_joker_count) = use_jokers(
current_kind,
joker_count.expect("While loop only iterates if there is some joke count left"),
);
current_kind = new_kind;
joker_count = new_joker_count;
}
current_kind
}
}
impl From<&'_ str> for HandKind {
fn from(value: &'_ str) -> Self {
let duplicated_counted = calc_duplicate_counted(&value, |_| true);
choose_kind_from(&duplicated_counted)
}
}
fn calc_duplicate_counted(
characters: &str,
on_consider_next_char: impl Fn(char) -> bool,
) -> Vec<usize> {
const FIRST_ENCOUNTERED: usize = 1;
let mut count_of_same: HashMap<char, usize> = HashMap::with_capacity(10);
for next_char in characters.chars() {
if on_consider_next_char(next_char) {
count_of_same
.entry(next_char)
.and_modify(|count| *count = *count + 1)
.or_insert(FIRST_ENCOUNTERED);
}
}
{
let mut to_sort: Vec<usize> = count_of_same.into_iter().map(|(_, count)| count).collect();
to_sort.sort_by_key(|&count| Reverse(count));
to_sort
}
}
fn choose_kind_from(to_choose_from: &[usize]) -> HandKind {
fn calc_kind(values: &[usize]) -> Option<HandKind> {
let max = values.first()?;
let kind = match max {
5 => HandKind::Five,
4 => HandKind::Four,
under_five_or_four => {
let opt_second_after_max = values.get(1);
match opt_second_after_max {
Some(second_after_max) => match (under_five_or_four, second_after_max) {
(3, 2) => HandKind::FullHouse,
(3, _) => HandKind::Three,
(2, 2) => HandKind::TwoPair,
(2, 1) => HandKind::OnePair,
_no_duplicates => HandKind::HighCard,
},
None => match under_five_or_four {
3 => HandKind::Three,
2 => HandKind::OnePair,
_no_duplicates => HandKind::HighCard,
},
}
}
};
Some(kind)
}
calc_kind(&to_choose_from).unwrap_or_else(|| {
warn!("Got hand ({:?}) with less than 2 cards", &to_choose_from);
let default_value = HandKind::default();
warn!("Default kind ({:?}) is assumed", default_value);
default_value
})
}
#[cfg(test)]
mod testing {
use crate::solutions::day7::{
categorized_hand::CategorizedHand, dealt_hand::DealtHand, hand_kind::HandKind,
};
#[test]
fn from_dealt_hand_to_categorized_hand() {
fn assert_case(given: DealtHand, expected: HandKind) {
let expected_hand = CategorizedHand::new(expected, given.clone());
let actual = given.into();
assert_eq!(expected_hand, actual);
}
assert_case(DealtHand::new("AAAAA", 20), HandKind::Five);
assert_case(DealtHand::new("AA8AA", 20), HandKind::Four);
assert_case(DealtHand::new("23332", 20), HandKind::FullHouse);
assert_case(DealtHand::new("TTT98", 20), HandKind::Three);
assert_case(DealtHand::new("23456", 20), HandKind::HighCard);
assert_case(DealtHand::new("23432", 20), HandKind::TwoPair);
assert_case(DealtHand::new("A23A4", 20), HandKind::OnePair);
}
#[test]
fn from_cards_to_kind_with_joker() {
const JOKER: char = 'J';
fn assert_case(given: &str, expected: HandKind) {
let actual: HandKind = (given, JOKER).into();
assert_eq!(expected, actual, "Given: {:?}", given);
}
//32T3K is still the only one pair;
//it doesn't contain any jokers, so its strength doesn't increase.
assert_case("32T3K", HandKind::OnePair);
//KK677 is now the only two pair, making it the second-weakest hand.
assert_case("KK677", HandKind::TwoPair);
//T55J5, KTJJT, and QQQJA are now all four of a kind! T55J5 gets rank 3,
//QQQJA gets rank 4, and KTJJT gets rank 5.
assert_case("T55J5", HandKind::Four);
assert_case("KTJJT", HandKind::Four);
assert_case("QQQJA", HandKind::Four);
// from highcard
assert_case("1234J", HandKind::OnePair);
assert_case("123JJ", HandKind::Three);
assert_case("12JJJ", HandKind::Four);
assert_case("1JJJJ", HandKind::Five);
assert_case("JJJJJ", HandKind::Five);
// from four
assert_case("QQQQJ", HandKind::Five);
assert_case("QQQQQ", HandKind::Five);
// from three
assert_case("QQQJJ", HandKind::Five);
// From two pairs
assert_case("QQTTJ", HandKind::FullHouse);
}
}

View file

@ -0,0 +1,32 @@
use derive_more::derive::Into;
#[derive(Debug, PartialEq, Eq, Into, Clone, Copy)]
pub struct NonZeroCount(usize);
impl std::ops::Sub for NonZeroCount {
type Output = Option<Self>;
fn sub(self, rhs: Self) -> Self::Output {
assert!(self.0 >= rhs.0);
Self::new(self.0 - rhs.0)
}
}
impl NonZeroCount {
pub fn one() -> Self {
Self(1)
}
pub fn decrement(&self) -> Option<Self> {
*self - Self::one()
}
pub fn new(value: usize) -> Option<Self> {
if value > 0 {
Some(Self(value))
} else {
None
}
}
}

View file

@ -2,29 +2,50 @@ use std::{cmp::Ordering, collections::HashMap, ops::RangeInclusive, sync::LazyLo
use log::warn; use log::warn;
const JOKER: char = 'J';
static LETTERS_WITHOUT_JOKER: &[char] = &['T', 'J', 'Q', 'K', 'A'];
static LETTERS: &[char] = &['T', 'J', 'Q', 'K', 'A']; static LETTERS: &[char] = &['T', 'J', 'Q', 'K', 'A'];
const NUMBERS: RangeInclusive<u32> = 0..=9; const NUMBERS: RangeInclusive<u32> = 0..=9;
static LABEL_ORDERING: LazyLock<HashMap<char, usize>> = LazyLock::new(|| { pub type StrengthOfSymbols = HashMap<char, usize>;
let numbers = NUMBERS
fn numbers_in_chars() -> impl Iterator<Item = char> {
NUMBERS
.into_iter() .into_iter()
.map(|number| std::char::from_digit(number, 10).unwrap()); .map(|number| std::char::from_digit(number, 10).unwrap())
numbers }
//A hand consists of five cards labeled one of A, K, Q, J, T, 9, 8, 7, 6, 5, 4, 3, or 2.
//The relative strength of each card follows this order, where A is the highest and 2 is the lowest.
pub static LABEL_ORDERING: LazyLock<StrengthOfSymbols> = LazyLock::new(|| {
numbers_in_chars()
.chain(LETTERS.into_iter().copied()) .chain(LETTERS.into_iter().copied())
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(index, character)| (character, index)) .map(|(index, character)| (character, index))
.collect() .collect()
}); });
//
//A hand consists of five cards labeled one of A, K, Q, J, T, 9, 8, 7, 6, 5, 4, 3, or 2. pub static LABEL_ORDERING_WITH_JOKER: LazyLock<StrengthOfSymbols> = LazyLock::new(|| {
//The relative strength of each card follows this order, where A is the highest and 2 is the lowest. std::iter::once(JOKER)
pub fn compare_along_str(left: &str, right: &str) -> Ordering { .chain(numbers_in_chars())
.chain(LETTERS_WITHOUT_JOKER.into_iter().copied())
.into_iter()
.enumerate()
.map(|(index, character)| (character, index))
.collect()
});
pub fn compare_along_str(
left: &str,
right: &str,
symbols_to_strength: &StrengthOfSymbols,
) -> Ordering {
for (left_char, right_char) in left.chars().zip(right.chars()) { for (left_char, right_char) in left.chars().zip(right.chars()) {
let left_rank_value = LABEL_ORDERING let left_rank_value = symbols_to_strength
.get(&left_char) .get(&left_char)
.unwrap_or_else(|| panic!("left char ({}) is not valid", left_char)); .unwrap_or_else(|| panic!("left char ({}) is not valid", left_char));
let right_rank_value = LABEL_ORDERING let right_rank_value = symbols_to_strength
.get(&right_char) .get(&right_char)
.unwrap_or_else(|| panic!("right char ({}) is not valid", right_char)); .unwrap_or_else(|| panic!("right char ({}) is not valid", right_char));