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![day5::solve_task_1, day5::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],

View file

@ -1,4 +1,4 @@
use categorized_hand::CategorizedHand;
use categorized_hand::{CategorizedHand, JokerOrdered};
mod categorized_hand;
mod dealt_hand;
@ -17,6 +17,26 @@ pub fn solve_task_1(input: &str) -> 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 {
sorted
.into_iter()
@ -52,4 +72,11 @@ mod testing {
let expected = "6440";
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 log::warn;
use derive_more::derive::Into;
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)]
pub struct CategorizedHand {
@ -12,7 +11,36 @@ pub struct CategorizedHand {
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 {
#[cfg(test)]
pub fn new(kind: HandKind, rest: DealtHand) -> Self {
Self { kind, rest }
}
pub fn rest(&self) -> &DealtHand {
&self.rest
}
@ -23,30 +51,42 @@ impl CategorizedHand {
impl Ord for CategorizedHand {
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 => {
assert!(
self.kind == other.kind,
left.kind == right.kind,
"If ordering is equal then the hand kind must be the same too\n\
Self kind: ({:?}) and other kind: ({:?})",
self.kind,
other.kind
left kind: ({:?}) and right kind: ({:?})",
left.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 => {
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\
Self kind: ({:?}) and other kind: ({:?})",
self.kind,
other.kind
left kind: ({:?}) and right kind: ({:?})",
left.kind,
right.kind
);
not_equal_ordering
}
}
}
}
impl PartialOrd for CategorizedHand {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
@ -56,59 +96,13 @@ impl PartialOrd for CategorizedHand {
impl From<DealtHand> for CategorizedHand {
fn from(value: DealtHand) -> Self {
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 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,
let kind = value.hand().into();
CategorizedHand { kind, rest: value }
}
}
};
Some(kind)
}
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
});
impl From<(DealtHand, char)> for CategorizedHand {
fn from((value, joker): (DealtHand, char)) -> Self {
let kind = (value.hand(), joker).into();
CategorizedHand { kind, rest: value }
}
}
@ -117,29 +111,7 @@ impl From<DealtHand> for CategorizedHand {
mod testing {
use std::cmp::Ordering;
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 {
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);
}
use crate::solutions::day7::{categorized_hand::CategorizedHand, dealt_hand::DealtHand};
#[test]
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)]
pub enum HandKind {
/// 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,
}
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;
const JOKER: char = 'J';
static LETTERS_WITHOUT_JOKER: &[char] = &['T', 'J', 'Q', 'K', 'A'];
static LETTERS: &[char] = &['T', 'J', 'Q', 'K', 'A'];
const NUMBERS: RangeInclusive<u32> = 0..=9;
static LABEL_ORDERING: LazyLock<HashMap<char, usize>> = LazyLock::new(|| {
let numbers = NUMBERS
pub type StrengthOfSymbols = HashMap<char, usize>;
fn numbers_in_chars() -> impl Iterator<Item = char> {
NUMBERS
.into_iter()
.map(|number| std::char::from_digit(number, 10).unwrap());
numbers
.map(|number| std::char::from_digit(number, 10).unwrap())
}
//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())
.into_iter()
.enumerate()
.map(|(index, character)| (character, index))
.collect()
});
//
//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 fn compare_along_str(left: &str, right: &str) -> Ordering {
pub static LABEL_ORDERING_WITH_JOKER: LazyLock<StrengthOfSymbols> = LazyLock::new(|| {
std::iter::once(JOKER)
.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()) {
let left_rank_value = LABEL_ORDERING
let left_rank_value = symbols_to_strength
.get(&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)
.unwrap_or_else(|| panic!("right char ({}) is not valid", right_char));