diff --git a/src/main.rs b/src/main.rs index 4276aa2..2eb2275 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use thiserror::Error; fn main() -> ExitCode { let args = AppCliArguments::parse(); + env_logger::init(); let solution = solve_given(&args); match solution { Ok(found_solution) => { diff --git a/src/solutions/day7.rs b/src/solutions/day7.rs index 4c16345..529c05e 100644 --- a/src/solutions/day7.rs +++ b/src/solutions/day7.rs @@ -4,8 +4,8 @@ mod categorized_hand; mod dealt_hand; mod hand_kind; mod parsing; +mod second_ordering; -/// TODO: wrong answer for 253639210 is too low for my puzzle input pub fn solve_task_1(input: &str) -> String { let parsed = parsing::parse_input(input); let sorted_and_categorized = { @@ -13,17 +13,23 @@ pub fn solve_task_1(input: &str) -> String { categorized.sort(); categorized }; - let total_score = calc_bid_and_ranks(&sorted_and_categorized); - total_score.to_string() + let total_winning = calc_total_winning(&sorted_and_categorized); + total_winning.to_string() } -fn calc_bid_and_ranks(sorted: &[CategorizedHand]) -> usize { +fn calc_total_winning(sorted: &[CategorizedHand]) -> usize { sorted .into_iter() .enumerate() .map(|(index, hand)| { - let rank = index + 1; - hand.score() * rank + let rank = index.saturating_add(1); + let score = hand.bid(); + score.checked_mul(rank).unwrap_or_else(|| { + panic!( + "Multiplication of score ({}) with rank ({}) resulted in overflow", + score, rank + ) + }) }) .sum() } diff --git a/src/solutions/day7/categorized_hand.rs b/src/solutions/day7/categorized_hand.rs index 2881450..262d194 100644 --- a/src/solutions/day7/categorized_hand.rs +++ b/src/solutions/day7/categorized_hand.rs @@ -1,9 +1,8 @@ -use std::{ - cmp::{Ordering, Reverse}, - collections::HashMap, - sync::LazyLock, - usize, -}; +use std::{cmp::Reverse, collections::HashMap, usize}; + +use log::warn; + +use crate::solutions::day7::second_ordering; use super::{dealt_hand::DealtHand, hand_kind::HandKind}; @@ -17,16 +16,34 @@ impl CategorizedHand { pub fn rest(&self) -> &DealtHand { &self.rest } - pub fn score(&self) -> usize { - self.rest().score() + pub fn bid(&self) -> usize { + self.rest().bid() } } impl Ord for CategorizedHand { fn cmp(&self, other: &Self) -> std::cmp::Ordering { match self.kind.cmp(&other.kind) { - std::cmp::Ordering::Equal => compare_along_str(self.rest().hand(), other.rest().hand()), - not_equal_ordering => not_equal_ordering, + std::cmp::Ordering::Equal => { + assert!( + self.kind == other.kind, + "If ordering is equal then the hand kind must be the same too\n\ + Self kind: ({:?}) and other kind: ({:?})", + self.kind, + other.kind + ); + second_ordering::compare_along_str(self.rest().hand(), other.rest().hand()) + } + not_equal_ordering => { + assert!( + self.kind != other.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 + ); + not_equal_ordering + } } } } @@ -37,63 +54,60 @@ impl PartialOrd for CategorizedHand { } } -static LABEL_ORDERING: LazyLock> = LazyLock::new(|| { - let numbers = (0..9) - .into_iter() - .map(|number| std::char::from_digit(number, 10).unwrap()); - let letters = ['T', 'J', 'Q', 'K', 'A']; - numbers - .chain(letters.into_iter()) - .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. -fn compare_along_str(left: &str, right: &str) -> Ordering { - for (left_char, right_char) in left.chars().zip(right.chars()) { - let left_rank_value = LABEL_ORDERING.get(&left_char); - let right_rank_value = LABEL_ORDERING.get(&right_char); - let current_ordering = left_rank_value.cmp(&right_rank_value); - if current_ordering != Ordering::Equal { - return current_ordering; - } - } - left.len().cmp(&right.len()) -} - impl From for CategorizedHand { fn from(value: DealtHand) -> Self { - let mut count_of_same: HashMap = 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(1); - } - let values = { - let mut to_sort: Vec = - count_of_same.into_iter().map(|(_, count)| count).collect(); - to_sort.sort_by_key(|&count| Reverse(count)); - to_sort - }; + fn calc_kind(values: &[usize]) -> Option { + let max = values.first()?; - let max = values.first().unwrap(); - let kind = match max { - 5 => HandKind::Five, - 4 => HandKind::Four, - _ => { - let second = values.get(1).unwrap(); - match (max, second) { - (3, 2) => HandKind::FullHouse, - (3, _) => HandKind::Three, - (2, 2) => HandKind::TwoPair, - (2, 1) => HandKind::OnePair, - _ => HandKind::HighCard, + 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, + } } + }; + + Some(kind) + } + + fn calc_duplicate_counted(value: &DealtHand) -> Vec { + const FIRST_ENCOUNTERED: usize = 1; + + let mut count_of_same: HashMap = 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 = + 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 } } @@ -128,7 +142,7 @@ mod testing { } #[test] - fn correct_cmp() { + fn compare_correctly_by_kind_and_second_ordering() { fn assert_case(left: DealtHand, right: DealtHand, expected: Ordering) { let (left, right): (CategorizedHand, CategorizedHand) = (left.into(), right.into()); let actual = left.cmp(&right); @@ -163,6 +177,28 @@ mod testing { DealtHand::new("KTJJT", 30), Ordering::Greater, ); + + //So, 33332 and 2AAAA are both four of a kind hands, + //but 33332 is stronger because its first card is stronger. + assert_case( + DealtHand::new("33332", 20), + DealtHand::new("2AAAA", 30), + Ordering::Greater, + ); + + //Similarly, 77888 and 77788 are both a full house, + //but 77888 is stronger because its third card is stronger + //(and both hands have the same first and second card). + assert_case( + DealtHand::new("77788", 30), + DealtHand::new("77888", 20), + Ordering::Less, + ); + assert_case( + DealtHand::new("19231", 30), + DealtHand::new("18231", 20), + Ordering::Greater, + ); } #[test] diff --git a/src/solutions/day7/dealt_hand.rs b/src/solutions/day7/dealt_hand.rs index 85e1b9e..9df5570 100644 --- a/src/solutions/day7/dealt_hand.rs +++ b/src/solutions/day7/dealt_hand.rs @@ -6,22 +6,22 @@ use thiserror::Error; #[derive(Debug, PartialEq, Eq, Clone)] pub struct DealtHand { hand: String, - score: usize, + bid: usize, } impl DealtHand { #[cfg(test)] pub fn new(hand: impl Into, score: usize) -> Self { let hand = hand.into(); - Self { hand, score } + Self { hand, bid: score } } pub fn hand(&self) -> &str { &self.hand } - pub fn score(&self) -> usize { - self.score + pub const fn bid(&self) -> usize { + self.bid } } @@ -44,7 +44,7 @@ impl FromStr for DealtHand { .parse() .map_err(|_| InvalidStrDealtHand::InvalidUnsignedNumberScore)?; let hand = hand.to_string(); - let done = DealtHand { hand, score }; + let done = DealtHand { hand, bid: score }; Ok(done) } _ => Err(InvalidStrDealtHand::Not2Elements), @@ -68,35 +68,35 @@ mod testing { "32T3K 765", Ok(DealtHand { hand: "32T3K".to_string(), - score: 765, + bid: 765, }), ); assert_case( "T55J5 684", Ok(DealtHand { hand: "T55J5".to_string(), - score: 684, + bid: 684, }), ); assert_case( "KK677 28", Ok(DealtHand { hand: "KK677".to_string(), - score: 28, + bid: 28, }), ); assert_case( "KTJJT 220", Ok(DealtHand { hand: "KTJJT".to_string(), - score: 220, + bid: 220, }), ); assert_case( "QQQJA 483", Ok(DealtHand { hand: "QQQJA".to_string(), - score: 483, + bid: 483, }), ); } diff --git a/src/solutions/day7/hand_kind.rs b/src/solutions/day7/hand_kind.rs index 29ac974..74bcd26 100644 --- a/src/solutions/day7/hand_kind.rs +++ b/src/solutions/day7/hand_kind.rs @@ -1,6 +1,7 @@ -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] pub enum HandKind { /// High card, where all cards' labels are distinct: 23456 + #[default] HighCard, /// One pair, where two cards share one label, and the other three cards have a different label from the pair and each other: A23A4 OnePair, diff --git a/src/solutions/day7/second_ordering.rs b/src/solutions/day7/second_ordering.rs new file mode 100644 index 0000000..7b3cebb --- /dev/null +++ b/src/solutions/day7/second_ordering.rs @@ -0,0 +1,45 @@ +use std::{cmp::Ordering, collections::HashMap, ops::RangeInclusive, sync::LazyLock, usize}; + +use log::warn; + +static LETTERS: &[char] = &['T', 'J', 'Q', 'K', 'A']; +const NUMBERS: RangeInclusive = 0..=9; + +static LABEL_ORDERING: LazyLock> = LazyLock::new(|| { + let numbers = NUMBERS + .into_iter() + .map(|number| std::char::from_digit(number, 10).unwrap()); + numbers + .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 { + for (left_char, right_char) in left.chars().zip(right.chars()) { + let left_rank_value = LABEL_ORDERING + .get(&left_char) + .unwrap_or_else(|| panic!("left char ({}) is not valid", left_char)); + let right_rank_value = LABEL_ORDERING + .get(&right_char) + .unwrap_or_else(|| panic!("right char ({}) is not valid", right_char)); + + let current_ordering = left_rank_value.cmp(&right_rank_value); + + let found_difference = current_ordering != Ordering::Equal; + if found_difference { + return current_ordering; + } + } + let (left_len, right_len) = (left.len(), right.len()); + warn!( + "Left and right seem to have no difference in strength\n\ + Now comparing via lenght ({}) ({}). By same length, lefte wins !", + left_len, right_len + ); + left_len.cmp(&right_len) +} diff --git a/src/solutions/day7/snapshots/advent_of_code_2023__solutions__day7__categorized_hand__testing__sort_example_correctly.snap b/src/solutions/day7/snapshots/advent_of_code_2023__solutions__day7__categorized_hand__testing__sort_example_correctly.snap index 5ad1575..8fe8023 100644 --- a/src/solutions/day7/snapshots/advent_of_code_2023__solutions__day7__categorized_hand__testing__sort_example_correctly.snap +++ b/src/solutions/day7/snapshots/advent_of_code_2023__solutions__day7__categorized_hand__testing__sort_example_correctly.snap @@ -7,35 +7,35 @@ expression: input kind: OnePair, rest: DealtHand { hand: "32T3K", - score: 765, + bid: 765, }, }, CategorizedHand { kind: TwoPair, rest: DealtHand { hand: "KTJJT", - score: 220, + bid: 220, }, }, CategorizedHand { kind: TwoPair, rest: DealtHand { hand: "KK677", - score: 28, + bid: 28, }, }, CategorizedHand { kind: Three, rest: DealtHand { hand: "T55J5", - score: 684, + bid: 684, }, }, CategorizedHand { kind: Three, rest: DealtHand { hand: "QQQJA", - score: 483, + bid: 483, }, }, ]