Compare commits

..

3 commits

Author SHA1 Message Date
BoolPurist
dad77e85c0 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
2024-08-23 09:23:00 +02:00
BoolPurist
d117c34786 Implemented faulty first part of day 7 2024-08-23 08:27:22 +02:00
BoolPurist
bb63ca7f74 Implemented line parsing for a dealt hand 2024-08-23 06:45:11 +02:00
10 changed files with 564 additions and 26 deletions

View file

@ -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) => {

View file

@ -1,5 +1,6 @@
pub mod day5; pub mod day5;
pub mod day6; pub mod day6;
pub mod day7;
pub fn create_solutions() -> Vec<Vec<fn(&str) -> String>> { pub fn create_solutions() -> Vec<Vec<fn(&str) -> String>> {
vec![ vec![
@ -9,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![not_implemented_yet, not_implemented_yet], vec![day7::solve_task_1, 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], vec![not_implemented_yet, not_implemented_yet],

View file

@ -1,4 +1,4 @@
use std::{str::FromStr}; use std::str::FromStr;
use thiserror::Error; use thiserror::Error;
@ -13,12 +13,6 @@ pub struct ItemMapping {
impl ItemMapping { impl ItemMapping {
fn offset_mapping(&self, to_map: UnsignedNumber) -> UnsignedNumber {
assert!(to_map >= self.source_range.start());
let difference = to_map - self.source_range.start();
self.target + difference
}
pub fn map_range(&self, to_map: SeedRange) -> MappedRange { pub fn map_range(&self, to_map: SeedRange) -> MappedRange {
let start = self.source_range.start(); let start = self.source_range.start();
let end = self.source_range.end(); let end = self.source_range.end();
@ -56,25 +50,9 @@ impl ItemMapping {
MappedRange::PartiallyWithin { outside, mapped } MappedRange::PartiallyWithin { outside, mapped }
} else if to_map.start() < start && to_map.end() > end { } else if to_map.start() < start && to_map.end() > end {
// -----yyy---- // -----yyy----
// ----xxxxxx // ----xxxxxx--
let left = { self.map_from_left_and_right(to_map)
let left_start = to_map.start();
let left_end = start - 1;
SeedRange::new(left_start, left_end)
};
let right = {
let right_start = end + 1;
let right_end = to_map.end();
SeedRange::new(right_start, right_end)
};
let mapped = {
let mapped_start = self.target;
let mapped_end = self.offset_mapping(end);
SeedRange::new(mapped_start, mapped_end)
};
MappedRange::FromBothSidePartiallyWithin { outside: (left, right), mapped }
} else { } else {
// ----yyyyyy---- // ----yyyyyy----
// -----xxxx----- // -----xxxx-----
@ -95,6 +73,37 @@ impl ItemMapping {
None None
} }
} }
fn map_from_left_and_right(&self, to_map: SeedRange) -> MappedRange {
let (start, end) = self.source_start_end();
let left = {
let left_start = to_map.start();
let left_end = start - 1;
SeedRange::new(left_start, left_end)
};
let right = {
let right_start = end + 1;
let right_end = to_map.end();
SeedRange::new(right_start, right_end)
};
let mapped = {
let mapped_start = self.target;
let mapped_end = self.offset_mapping(end);
SeedRange::new(mapped_start, mapped_end)
};
MappedRange::FromBothSidePartiallyWithin { outside: (left, right), mapped }
}
fn source_start_end(&self) -> (UnsignedNumber, UnsignedNumber) {
let range = self.source_range;
(range.start(), range.end())
}
fn offset_mapping(&self, to_map: UnsignedNumber) -> UnsignedNumber {
assert!(to_map >= self.source_range.start());
let difference = to_map - self.source_range.start();
self.target + difference
}
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]

55
src/solutions/day7.rs Normal file
View file

@ -0,0 +1,55 @@
use categorized_hand::CategorizedHand;
mod categorized_hand;
mod dealt_hand;
mod hand_kind;
mod parsing;
mod second_ordering;
pub fn solve_task_1(input: &str) -> String {
let parsed = parsing::parse_input(input);
let sorted_and_categorized = {
let mut categorized: Vec<_> = parsed.into_iter().map(CategorizedHand::from).collect();
categorized.sort();
categorized
};
let total_winning = calc_total_winning(&sorted_and_categorized);
total_winning.to_string()
}
fn calc_total_winning(sorted: &[CategorizedHand]) -> usize {
sorted
.into_iter()
.enumerate()
.map(|(index, hand)| {
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()
}
#[cfg(test)]
const TEST_INPUT: &str = "32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483
";
#[cfg(test)]
mod testing {
use super::TEST_INPUT;
#[test]
fn solve_example_according_to_task_1() {
let actual = super::solve_task_1(TEST_INPUT);
let expected = "6440";
assert_eq!(expected, actual);
}
}

View file

@ -0,0 +1,230 @@
use std::{cmp::Reverse, collections::HashMap, usize};
use log::warn;
use crate::solutions::day7::second_ordering;
use super::{dealt_hand::DealtHand, hand_kind::HandKind};
#[derive(Debug, PartialEq, Eq)]
pub struct CategorizedHand {
kind: HandKind,
rest: DealtHand,
}
impl CategorizedHand {
pub fn rest(&self) -> &DealtHand {
&self.rest
}
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 => {
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
}
}
}
}
impl PartialOrd for CategorizedHand {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
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,
}
}
};
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 }
}
}
#[cfg(test)]
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);
}
#[test]
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);
assert_eq!(expected, actual, "Left: {:?}\nRight: {:?}", left, right);
}
assert_case(
DealtHand::new("AAAAA", 20),
DealtHand::new("BBBBA", 30),
Ordering::Greater,
);
assert_case(
DealtHand::new("12345", 20),
DealtHand::new("BBBBA", 30),
Ordering::Less,
);
assert_case(
DealtHand::new("12345", 20),
DealtHand::new("28495", 30),
Ordering::Less,
);
assert_case(
DealtHand::new("QQQJA", 20),
DealtHand::new("T55J5", 30),
Ordering::Greater,
);
//KK677 and KTJJT are both two pair.
//Their first cards both have the same label,
//but the second card of KK677 is stronger (K vs T), so KTJJT gets rank 2 and KK677 gets rank 3.
assert_case(
DealtHand::new("KK677", 20),
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]
fn sort_example_correctly() {
//32T3K is the only one pair and the other hands are all a stronger type, so it gets rank 1.
//
//KK677 and KTJJT are both two pair.
//Their first cards both have the same label,
//but the second card of KK677 is stronger (K vs T), so KTJJT gets rank 2 and KK677 gets rank 3.
//
//T55J5 and QQQJA are both three of a kind.
//QQQJA has a stronger first card, so it gets rank 5 and T55J5 gets rank 4.
let mut input: Vec<_> = vec![
DealtHand::new("32T3K", 765),
DealtHand::new("T55J5", 684),
DealtHand::new("KK677", 28),
DealtHand::new("KTJJT", 220),
DealtHand::new("QQQJA", 483),
]
.into_iter()
.map(CategorizedHand::from)
.collect();
input.sort();
insta::assert_debug_snapshot!(input);
}
}

View file

@ -0,0 +1,115 @@
use std::{str::FromStr, usize};
use derive_more::derive::Debug;
use thiserror::Error;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DealtHand {
hand: String,
bid: usize,
}
impl DealtHand {
#[cfg(test)]
pub fn new(hand: impl Into<String>, score: usize) -> Self {
let hand = hand.into();
Self { hand, bid: score }
}
pub fn hand(&self) -> &str {
&self.hand
}
pub const fn bid(&self) -> usize {
self.bid
}
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum InvalidStrDealtHand {
#[error("Dealt hand must consist of 2 elements")]
Not2Elements,
#[error("Score must be an unsigned number")]
InvalidUnsignedNumberScore,
}
impl FromStr for DealtHand {
type Err = InvalidStrDealtHand;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut in_two = s.split_whitespace();
match (in_two.next(), in_two.next()) {
(Some(hand), Some(score)) => {
let score: usize = score
.parse()
.map_err(|_| InvalidStrDealtHand::InvalidUnsignedNumberScore)?;
let hand = hand.to_string();
let done = DealtHand { hand, bid: score };
Ok(done)
}
_ => Err(InvalidStrDealtHand::Not2Elements),
}
}
}
#[cfg(test)]
mod testing {
use crate::solutions::day7::dealt_hand::{DealtHand, InvalidStrDealtHand};
#[test]
fn parse_str() {
fn assert_case(given: &str, expected: Result<DealtHand, InvalidStrDealtHand>) {
let actual = given.parse();
assert_eq!(expected, actual, "Input: {}", given);
}
fn happy_paths() {
assert_case(
"32T3K 765",
Ok(DealtHand {
hand: "32T3K".to_string(),
bid: 765,
}),
);
assert_case(
"T55J5 684",
Ok(DealtHand {
hand: "T55J5".to_string(),
bid: 684,
}),
);
assert_case(
"KK677 28",
Ok(DealtHand {
hand: "KK677".to_string(),
bid: 28,
}),
);
assert_case(
"KTJJT 220",
Ok(DealtHand {
hand: "KTJJT".to_string(),
bid: 220,
}),
);
assert_case(
"QQQJA 483",
Ok(DealtHand {
hand: "QQQJA".to_string(),
bid: 483,
}),
);
}
fn error_paths() {
assert_case(
"xxxxx aaa",
Err(InvalidStrDealtHand::InvalidUnsignedNumberScore),
);
assert_case("xxxxx", Err(InvalidStrDealtHand::Not2Elements));
}
happy_paths();
error_paths();
}
}

View file

@ -0,0 +1,18 @@
#[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,
/// Two pair, where two cards share one label, two other cards share a second label, and the remaining card has a third label: 23432
TwoPair,
/// Three of a kind, where three cards have the same label, and the remaining two cards are each different from any other card in the hand: TTT98
Three,
/// Full house, where three cards have the same label, and the remaining two cards share a different label: 23332
FullHouse,
/// Four of a kind, where four cards have the same label and one card has a different label: AA8AA
Four,
/// Five of a kind, where all five cards have the same label: AAAAA
Five,
}

View file

@ -0,0 +1,23 @@
use super::dealt_hand::DealtHand;
pub fn parse_input(input: &str) -> Vec<DealtHand> {
input.lines().map(|line| line.parse().unwrap()).collect()
}
#[cfg(test)]
mod testing {
use crate::solutions::day7::{dealt_hand::DealtHand, TEST_INPUT};
#[test]
fn parse() {
let actual = super::parse_input(TEST_INPUT);
let expected = vec![
DealtHand::new("32T3K", 765),
DealtHand::new("T55J5", 684),
DealtHand::new("KK677", 28),
DealtHand::new("KTJJT", 220),
DealtHand::new("QQQJA", 483),
];
assert_eq!(expected, actual);
}
}

View 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)
}

View file

@ -0,0 +1,41 @@
---
source: src/solutions/day7/categorized_hand.rs
expression: input
---
[
CategorizedHand {
kind: OnePair,
rest: DealtHand {
hand: "32T3K",
bid: 765,
},
},
CategorizedHand {
kind: TwoPair,
rest: DealtHand {
hand: "KTJJT",
bid: 220,
},
},
CategorizedHand {
kind: TwoPair,
rest: DealtHand {
hand: "KK677",
bid: 28,
},
},
CategorizedHand {
kind: Three,
rest: DealtHand {
hand: "T55J5",
bid: 684,
},
},
CategorizedHand {
kind: Three,
rest: DealtHand {
hand: "QQQJA",
bid: 483,
},
},
]