Solved part 1 of day 7
Fix: numbers for strength in second ordering was an exclusive range instead of an inclusive range Tidied up code for readability
This commit is contained in:
parent
d117c34786
commit
dad77e85c0
7 changed files with 174 additions and 85 deletions
|
@ -7,6 +7,7 @@ use thiserror::Error;
|
||||||
|
|
||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
let args = AppCliArguments::parse();
|
let args = AppCliArguments::parse();
|
||||||
|
env_logger::init();
|
||||||
let solution = solve_given(&args);
|
let solution = solve_given(&args);
|
||||||
match solution {
|
match solution {
|
||||||
Ok(found_solution) => {
|
Ok(found_solution) => {
|
||||||
|
|
|
@ -4,8 +4,8 @@ mod categorized_hand;
|
||||||
mod dealt_hand;
|
mod dealt_hand;
|
||||||
mod hand_kind;
|
mod hand_kind;
|
||||||
mod parsing;
|
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 {
|
pub fn solve_task_1(input: &str) -> String {
|
||||||
let parsed = parsing::parse_input(input);
|
let parsed = parsing::parse_input(input);
|
||||||
let sorted_and_categorized = {
|
let sorted_and_categorized = {
|
||||||
|
@ -13,17 +13,23 @@ pub fn solve_task_1(input: &str) -> String {
|
||||||
categorized.sort();
|
categorized.sort();
|
||||||
categorized
|
categorized
|
||||||
};
|
};
|
||||||
let total_score = calc_bid_and_ranks(&sorted_and_categorized);
|
let total_winning = calc_total_winning(&sorted_and_categorized);
|
||||||
total_score.to_string()
|
total_winning.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calc_bid_and_ranks(sorted: &[CategorizedHand]) -> usize {
|
fn calc_total_winning(sorted: &[CategorizedHand]) -> usize {
|
||||||
sorted
|
sorted
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, hand)| {
|
.map(|(index, hand)| {
|
||||||
let rank = index + 1;
|
let rank = index.saturating_add(1);
|
||||||
hand.score() * rank
|
let score = hand.bid();
|
||||||
|
score.checked_mul(rank).unwrap_or_else(|| {
|
||||||
|
panic!(
|
||||||
|
"Multiplication of score ({}) with rank ({}) resulted in overflow",
|
||||||
|
score, rank
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.sum()
|
.sum()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
use std::{
|
use std::{cmp::Reverse, collections::HashMap, usize};
|
||||||
cmp::{Ordering, Reverse},
|
|
||||||
collections::HashMap,
|
use log::warn;
|
||||||
sync::LazyLock,
|
|
||||||
usize,
|
use crate::solutions::day7::second_ordering;
|
||||||
};
|
|
||||||
|
|
||||||
use super::{dealt_hand::DealtHand, hand_kind::HandKind};
|
use super::{dealt_hand::DealtHand, hand_kind::HandKind};
|
||||||
|
|
||||||
|
@ -17,16 +16,34 @@ impl CategorizedHand {
|
||||||
pub fn rest(&self) -> &DealtHand {
|
pub fn rest(&self) -> &DealtHand {
|
||||||
&self.rest
|
&self.rest
|
||||||
}
|
}
|
||||||
pub fn score(&self) -> usize {
|
pub fn bid(&self) -> usize {
|
||||||
self.rest().score()
|
self.rest().bid()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
match self.kind.cmp(&other.kind) {
|
||||||
std::cmp::Ordering::Equal => compare_along_str(self.rest().hand(), other.rest().hand()),
|
std::cmp::Ordering::Equal => {
|
||||||
not_equal_ordering => not_equal_ordering,
|
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,64 +54,61 @@ impl PartialOrd for CategorizedHand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static LABEL_ORDERING: LazyLock<HashMap<char, usize>> = 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<DealtHand> for CategorizedHand {
|
impl From<DealtHand> for CategorizedHand {
|
||||||
fn from(value: DealtHand) -> Self {
|
fn from(value: DealtHand) -> Self {
|
||||||
let mut count_of_same: HashMap<char, usize> = HashMap::with_capacity(10);
|
fn calc_kind(values: &[usize]) -> Option<HandKind> {
|
||||||
for next_char in value.hand().chars() {
|
let max = values.first()?;
|
||||||
count_of_same
|
|
||||||
.entry(next_char)
|
|
||||||
.and_modify(|count| *count = *count + 1)
|
|
||||||
.or_insert(1);
|
|
||||||
}
|
|
||||||
let values = {
|
|
||||||
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 max = values.first().unwrap();
|
|
||||||
let kind = match max {
|
let kind = match max {
|
||||||
5 => HandKind::Five,
|
5 => HandKind::Five,
|
||||||
4 => HandKind::Four,
|
4 => HandKind::Four,
|
||||||
_ => {
|
under_five_or_four => {
|
||||||
let second = values.get(1).unwrap();
|
let second_after_max = values.get(1)?;
|
||||||
match (max, second) {
|
match (under_five_or_four, second_after_max) {
|
||||||
(3, 2) => HandKind::FullHouse,
|
(3, 2) => HandKind::FullHouse,
|
||||||
(3, _) => HandKind::Three,
|
(3, _) => HandKind::Three,
|
||||||
(2, 2) => HandKind::TwoPair,
|
(2, 2) => HandKind::TwoPair,
|
||||||
(2, 1) => HandKind::OnePair,
|
(2, 1) => HandKind::OnePair,
|
||||||
_ => HandKind::HighCard,
|
_no_duplicates => HandKind::HighCard,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
|
||||||
CategorizedHand { kind, rest: value }
|
CategorizedHand { kind, rest: value }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,7 +142,7 @@ mod testing {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn correct_cmp() {
|
fn compare_correctly_by_kind_and_second_ordering() {
|
||||||
fn assert_case(left: DealtHand, right: DealtHand, expected: Ordering) {
|
fn assert_case(left: DealtHand, right: DealtHand, expected: Ordering) {
|
||||||
let (left, right): (CategorizedHand, CategorizedHand) = (left.into(), right.into());
|
let (left, right): (CategorizedHand, CategorizedHand) = (left.into(), right.into());
|
||||||
let actual = left.cmp(&right);
|
let actual = left.cmp(&right);
|
||||||
|
@ -163,6 +177,28 @@ mod testing {
|
||||||
DealtHand::new("KTJJT", 30),
|
DealtHand::new("KTJJT", 30),
|
||||||
Ordering::Greater,
|
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]
|
#[test]
|
||||||
|
|
|
@ -6,22 +6,22 @@ use thiserror::Error;
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct DealtHand {
|
pub struct DealtHand {
|
||||||
hand: String,
|
hand: String,
|
||||||
score: usize,
|
bid: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DealtHand {
|
impl DealtHand {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn new(hand: impl Into<String>, score: usize) -> Self {
|
pub fn new(hand: impl Into<String>, score: usize) -> Self {
|
||||||
let hand = hand.into();
|
let hand = hand.into();
|
||||||
Self { hand, score }
|
Self { hand, bid: score }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hand(&self) -> &str {
|
pub fn hand(&self) -> &str {
|
||||||
&self.hand
|
&self.hand
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn score(&self) -> usize {
|
pub const fn bid(&self) -> usize {
|
||||||
self.score
|
self.bid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ impl FromStr for DealtHand {
|
||||||
.parse()
|
.parse()
|
||||||
.map_err(|_| InvalidStrDealtHand::InvalidUnsignedNumberScore)?;
|
.map_err(|_| InvalidStrDealtHand::InvalidUnsignedNumberScore)?;
|
||||||
let hand = hand.to_string();
|
let hand = hand.to_string();
|
||||||
let done = DealtHand { hand, score };
|
let done = DealtHand { hand, bid: score };
|
||||||
Ok(done)
|
Ok(done)
|
||||||
}
|
}
|
||||||
_ => Err(InvalidStrDealtHand::Not2Elements),
|
_ => Err(InvalidStrDealtHand::Not2Elements),
|
||||||
|
@ -68,35 +68,35 @@ mod testing {
|
||||||
"32T3K 765",
|
"32T3K 765",
|
||||||
Ok(DealtHand {
|
Ok(DealtHand {
|
||||||
hand: "32T3K".to_string(),
|
hand: "32T3K".to_string(),
|
||||||
score: 765,
|
bid: 765,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
assert_case(
|
assert_case(
|
||||||
"T55J5 684",
|
"T55J5 684",
|
||||||
Ok(DealtHand {
|
Ok(DealtHand {
|
||||||
hand: "T55J5".to_string(),
|
hand: "T55J5".to_string(),
|
||||||
score: 684,
|
bid: 684,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
assert_case(
|
assert_case(
|
||||||
"KK677 28",
|
"KK677 28",
|
||||||
Ok(DealtHand {
|
Ok(DealtHand {
|
||||||
hand: "KK677".to_string(),
|
hand: "KK677".to_string(),
|
||||||
score: 28,
|
bid: 28,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
assert_case(
|
assert_case(
|
||||||
"KTJJT 220",
|
"KTJJT 220",
|
||||||
Ok(DealtHand {
|
Ok(DealtHand {
|
||||||
hand: "KTJJT".to_string(),
|
hand: "KTJJT".to_string(),
|
||||||
score: 220,
|
bid: 220,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
assert_case(
|
assert_case(
|
||||||
"QQQJA 483",
|
"QQQJA 483",
|
||||||
Ok(DealtHand {
|
Ok(DealtHand {
|
||||||
hand: "QQQJA".to_string(),
|
hand: "QQQJA".to_string(),
|
||||||
score: 483,
|
bid: 483,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[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
|
||||||
|
#[default]
|
||||||
HighCard,
|
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
|
/// 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,
|
OnePair,
|
||||||
|
|
45
src/solutions/day7/second_ordering.rs
Normal file
45
src/solutions/day7/second_ordering.rs
Normal file
|
@ -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<u32> = 0..=9;
|
||||||
|
|
||||||
|
static LABEL_ORDERING: LazyLock<HashMap<char, usize>> = 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)
|
||||||
|
}
|
|
@ -7,35 +7,35 @@ expression: input
|
||||||
kind: OnePair,
|
kind: OnePair,
|
||||||
rest: DealtHand {
|
rest: DealtHand {
|
||||||
hand: "32T3K",
|
hand: "32T3K",
|
||||||
score: 765,
|
bid: 765,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CategorizedHand {
|
CategorizedHand {
|
||||||
kind: TwoPair,
|
kind: TwoPair,
|
||||||
rest: DealtHand {
|
rest: DealtHand {
|
||||||
hand: "KTJJT",
|
hand: "KTJJT",
|
||||||
score: 220,
|
bid: 220,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CategorizedHand {
|
CategorizedHand {
|
||||||
kind: TwoPair,
|
kind: TwoPair,
|
||||||
rest: DealtHand {
|
rest: DealtHand {
|
||||||
hand: "KK677",
|
hand: "KK677",
|
||||||
score: 28,
|
bid: 28,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CategorizedHand {
|
CategorizedHand {
|
||||||
kind: Three,
|
kind: Three,
|
||||||
rest: DealtHand {
|
rest: DealtHand {
|
||||||
hand: "T55J5",
|
hand: "T55J5",
|
||||||
score: 684,
|
bid: 684,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CategorizedHand {
|
CategorizedHand {
|
||||||
kind: Three,
|
kind: Three,
|
||||||
rest: DealtHand {
|
rest: DealtHand {
|
||||||
hand: "QQQJA",
|
hand: "QQQJA",
|
||||||
score: 483,
|
bid: 483,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
Loading…
Add table
Reference in a new issue