From 312079787337a7fcadaef61a5961e92869816eb9 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Thu, 7 Dec 2023 21:03:53 +0100 Subject: [PATCH] things --- 2023/day07/src/lib.rs | 96 +++++++++++++------------ 2023/day07/src/sort_key.rs | 111 ++++++++++++++++++++++++++++ 2023/day07/src/unstable_sort.rs | 124 ++++++++++++++++++++++++++++++++ 3 files changed, 286 insertions(+), 45 deletions(-) create mode 100644 2023/day07/src/sort_key.rs create mode 100644 2023/day07/src/unstable_sort.rs diff --git a/2023/day07/src/lib.rs b/2023/day07/src/lib.rs index 601e064..66ce6ac 100644 --- a/2023/day07/src/lib.rs +++ b/2023/day07/src/lib.rs @@ -1,3 +1,6 @@ +mod sort_key; +mod unstable_sort; + use std::cmp::{self, Ordering}; use helper::{Day, IteratorExt, Variants}; @@ -11,10 +14,14 @@ struct Day07; helper::define_variants! { day => crate::Day07; part1 { - basic => crate::part1; + basic => crate::part1, sample_count=10000; + unstable_sort => crate::unstable_sort::part1, sample_count=10000; + card_soa => crate::sort_key::part1, sample_count=10000; } part2 { - basic => crate::part2; + basic => crate::part2, sample_count=10000; + unstable_sort => crate::unstable_sort::part2, sample_count=10000; + card_soa => crate::sort_key::part2, sample_count=10000; } } @@ -28,7 +35,7 @@ impl Day for Day07 { } } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] enum HandType { FiveSame = 0, FourSame, @@ -46,48 +53,47 @@ struct Hand { bid: u64, } -impl HandType { - fn of(hand: [u8; 5], has_jokers: bool) -> Self { - let mut map: [Option<(u8, u8)>; 5] = [None; 5]; - let mut jokers = 0; +fn hand_type_of(hand: [u8; 5], has_jokers: bool) -> HandType { + let mut card_type = [0; 5]; + let mut counts = [0; 5]; + let mut jokers = 0; - for card in hand { - if card == b'J' && has_jokers { - jokers += 1; - continue; - } - if let Some(existing) = map.iter_mut().find(|c| c.is_some_and(|c| c.0 == card)) { - existing.as_mut().unwrap().1 += 1; - } else { - let idx = map.iter().position(|c| c.is_none()).unwrap(); - map[idx] = Some((card, 1)); - } + for card in hand { + if card == b'J' && has_jokers { + jokers += 1; + continue; } - map.sort_by_key(|c| cmp::Reverse(c.map(|c| c.1))); - - let map = map.map(|c| c.map_or(0, |c| c.1)); - if map[0] + jokers == 5 { - Self::FiveSame - } else if map[0] + jokers == 4 { - Self::FourSame - // If there are only cards of two types + jokers, then we can form a full house - // no matter how they're set up. - } else if map[0] + map[1] + jokers == 5 { - Self::FullHouse - } else if map[0] + jokers == 3 { - Self::ThreeSame - // We need four cards for a two pair. Given that the previous constellations - // are not possible, we are able to build a two pair. - } else if map[0] + map[1] + jokers == 4 { - Self::TwoPair - // Fun fact: This == is fine and needn't be >=, because if map[0]+jokers==3, - // then we can build two pairs. - } else if map[0] + jokers == 2 { - Self::OnePair + if let Some(existing) = card_type.into_iter().position(|c| c == card) { + counts[existing] += 1; } else { - Self::HighCard + let idx = card_type.into_iter().position(|c| c == 0).unwrap(); + card_type[idx] = card; + counts[idx] = 1_u8; } } + counts.sort_by_key(|&c| cmp::Reverse(c)); + + if counts[0] + jokers == 5 { + HandType::FiveSame + } else if counts[0] + jokers == 4 { + HandType::FourSame + // If there are only cards of two types + jokers, then we can form a full house + // no matter how they're set up. + } else if counts[0] + counts[1] + jokers == 5 { + HandType::FullHouse + } else if counts[0] + jokers == 3 { + HandType::ThreeSame + // We need four cards for a two pair. Given that the previous constellations + // are not possible, we are able to build a two pair. + } else if counts[0] + counts[1] + jokers == 4 { + HandType::TwoPair + // Fun fact: This == is fine and needn't be >=, because if map[0]+jokers==3, + // then we can build two pairs. + } else if counts[0] + jokers == 2 { + HandType::OnePair + } else { + HandType::HighCard + } } #[cfg(test)] @@ -96,9 +102,9 @@ mod tests { #[test] fn hand_type() { - assert_eq!(HandType::of(*b"32T3K", false), HandType::OnePair); - assert_eq!(HandType::of(*b"K6KK6", false), HandType::FullHouse); - assert_eq!(HandType::of(*b"KTJJT", true), HandType::FourSame); + assert_eq!(super::hand_type_of(*b"32T3K", false), HandType::OnePair); + assert_eq!(super::hand_type_of(*b"K6KK6", false), HandType::FullHouse); + assert_eq!(super::hand_type_of(*b"KTJJT", true), HandType::FourSame); } } @@ -111,7 +117,7 @@ fn parse(input: &str, has_jokers: bool) -> Vec { let bid = parts.next().unwrap().parse().unwrap(); let values = cards.bytes().collect_array::<5>().unwrap(); - let hand_type = HandType::of(values, has_jokers); + let hand_type = hand_type_of(values, has_jokers); Hand { values, @@ -174,7 +180,7 @@ helper::tests! { } part2 { small => 5905; - default => 0; + default => 248781813; } } helper::benchmarks! {} diff --git a/2023/day07/src/sort_key.rs b/2023/day07/src/sort_key.rs new file mode 100644 index 0000000..f7d0a86 --- /dev/null +++ b/2023/day07/src/sort_key.rs @@ -0,0 +1,111 @@ +use std::cmp::{self}; + +use helper::IteratorExt; + +use crate::{Hand, HandType}; + +fn hand_type_of(hand: [u8; 5], has_jokers: bool) -> HandType { + let mut card_type = [0; 5]; + let mut counts = [0; 5]; + let mut jokers = 0; + + for card in hand { + if card == b'J' && has_jokers { + jokers += 1; + continue; + } + if let Some(existing) = card_type.into_iter().position(|c| c == card) { + counts[existing] += 1; + } else { + let idx = card_type.into_iter().position(|c| c == 0).unwrap(); + card_type[idx] = card; + counts[idx] = 1_u8; + } + } + counts.sort_unstable_by_key(|&c| cmp::Reverse(c)); + + if counts[0] + jokers == 5 { + HandType::FiveSame + } else if counts[0] + jokers == 4 { + HandType::FourSame + // If there are only cards of two types + jokers, then we can form a full house + // no matter how they're set up. + } else if counts[0] + counts[1] + jokers == 5 { + HandType::FullHouse + } else if counts[0] + jokers == 3 { + HandType::ThreeSame + // We need four cards for a two pair. Given that the previous constellations + // are not possible, we are able to build a two pair. + } else if counts[0] + counts[1] + jokers == 4 { + HandType::TwoPair + // Fun fact: This == is fine and needn't be >=, because if map[0]+jokers==3, + // then we can build two pairs. + } else if counts[0] + jokers == 2 { + HandType::OnePair + } else { + HandType::HighCard + } +} + +#[cfg(test)] +mod tests { + use crate::HandType; + + #[test] + fn hand_type() { + assert_eq!(super::hand_type_of(*b"32T3K", false), HandType::OnePair); + assert_eq!(super::hand_type_of(*b"K6KK6", false), HandType::FullHouse); + assert_eq!(super::hand_type_of(*b"KTJJT", true), HandType::FourSame); + } +} + +fn parse(input: &str, has_jokers: bool) -> Vec { + input + .lines() + .map(|line| { + let mut parts = line.split_ascii_whitespace(); + let cards = parts.next().unwrap(); + let bid = parts.next().unwrap().parse().unwrap(); + + let values = cards.bytes().collect_array::<5>().unwrap(); + let hand_type = hand_type_of(values, has_jokers); + + Hand { + values, + hand_type, + bid, + } + }) + .collect() +} + +fn evaluate_hands(hands: &mut [Hand], has_jokers: bool) -> u64 { + // Worst hand first, best hand last. + hands.sort_unstable_by_key(|hand| { + let mk_compare = |v| match v { + b'A' => b'Z', + b'K' => b'Y', + b'T' => b'I', + b'J' if has_jokers => b'0', + other => other, + }; + + cmp::Reverse((hand.hand_type, cmp::Reverse(hand.values.map(mk_compare)))) + }); + + hands + .iter() + .zip(1_u64..) + .map(|(hand, i)| hand.bid * i) + .sum() +} + +pub fn part1(input: &str) -> u64 { + let mut hands = parse(input, false); + evaluate_hands(&mut hands, false) +} + +pub fn part2(input: &str) -> u64 { + let mut hands = parse(input, true); + evaluate_hands(&mut hands, true) +} diff --git a/2023/day07/src/unstable_sort.rs b/2023/day07/src/unstable_sort.rs new file mode 100644 index 0000000..9cadd08 --- /dev/null +++ b/2023/day07/src/unstable_sort.rs @@ -0,0 +1,124 @@ +use std::cmp::{self, Ordering}; + +use helper::IteratorExt; + +use crate::{Hand, HandType}; + +fn hand_type_of(hand: [u8; 5], has_jokers: bool) -> HandType { + let mut card_type = [0; 5]; + let mut counts = [0; 5]; + let mut jokers = 0; + + for card in hand { + if card == b'J' && has_jokers { + jokers += 1; + continue; + } + if let Some(existing) = card_type.into_iter().position(|c| c == card) { + counts[existing] += 1; + } else { + let idx = card_type.into_iter().position(|c| c == 0).unwrap(); + card_type[idx] = card; + counts[idx] = 1_u8; + } + } + counts.sort_unstable_by_key(|&c| cmp::Reverse(c)); + + if counts[0] + jokers == 5 { + HandType::FiveSame + } else if counts[0] + jokers == 4 { + HandType::FourSame + // If there are only cards of two types + jokers, then we can form a full house + // no matter how they're set up. + } else if counts[0] + counts[1] + jokers == 5 { + HandType::FullHouse + } else if counts[0] + jokers == 3 { + HandType::ThreeSame + // We need four cards for a two pair. Given that the previous constellations + // are not possible, we are able to build a two pair. + } else if counts[0] + counts[1] + jokers == 4 { + HandType::TwoPair + // Fun fact: This == is fine and needn't be >=, because if map[0]+jokers==3, + // then we can build two pairs. + } else if counts[0] + jokers == 2 { + HandType::OnePair + } else { + HandType::HighCard + } +} + +#[cfg(test)] +mod tests { + use crate::HandType; + + #[test] + fn hand_type() { + assert_eq!(super::hand_type_of(*b"32T3K", false), HandType::OnePair); + assert_eq!(super::hand_type_of(*b"K6KK6", false), HandType::FullHouse); + assert_eq!(super::hand_type_of(*b"KTJJT", true), HandType::FourSame); + } +} + +fn parse(input: &str, has_jokers: bool) -> Vec { + input + .lines() + .map(|line| { + let mut parts = line.split_ascii_whitespace(); + let cards = parts.next().unwrap(); + let bid = parts.next().unwrap().parse().unwrap(); + + let values = cards.bytes().collect_array::<5>().unwrap(); + let hand_type = hand_type_of(values, has_jokers); + + Hand { + values, + hand_type, + bid, + } + }) + .collect() +} + +fn evaluate_hands(hands: &mut [Hand], has_jokers: bool) -> u64 { + // Worst hand first, best hand last. + hands.sort_unstable_by(|a, b| { + let mk_compare = |v| match v { + b'A' => b'Z', + b'K' => b'Y', + b'T' => b'I', + b'J' if has_jokers => b'0', + other => other, + }; + + let types = a.hand_type.cmp(&b.hand_type); + if types != Ordering::Equal { + return types.reverse(); + } + for (a, b) in std::iter::zip(a.values, b.values) { + if a == b { + continue; + } + let a = mk_compare(a); + let b = mk_compare(b); + + return a.cmp(&b); + } + Ordering::Equal + }); + + hands + .iter() + .zip(1_u64..) + .map(|(hand, i)| hand.bid * i) + .sum() +} + +pub fn part1(input: &str) -> u64 { + let mut hands = parse(input, false); + evaluate_hands(&mut hands, false) +} + +pub fn part2(input: &str) -> u64 { + let mut hands = parse(input, true); + evaluate_hands(&mut hands, true) +}