Solved part 2 of day 7 partially
Example works but not the real puzzle input
This commit is contained in:
parent
dad77e85c0
commit
480bc48d79
7 changed files with 341 additions and 113 deletions
5
file_input/day7_example.txt
Normal file
5
file_input/day7_example.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
32T3K 765
|
||||
T55J5 684
|
||||
KK677 28
|
||||
KTJJT 220
|
||||
QQQJA 483
|
|
@ -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],
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,29 +51,41 @@ 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 {
|
||||
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
32
src/solutions/day7/hand_kind/non_zero_count.rs
Normal file
32
src/solutions/day7/hand_kind/non_zero_count.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue