This commit is contained in:
nora 2023-12-07 21:03:53 +01:00
parent f88364a895
commit 3120797873
3 changed files with 286 additions and 45 deletions

View file

@ -1,3 +1,6 @@
mod sort_key;
mod unstable_sort;
use std::cmp::{self, Ordering}; use std::cmp::{self, Ordering};
use helper::{Day, IteratorExt, Variants}; use helper::{Day, IteratorExt, Variants};
@ -11,10 +14,14 @@ struct Day07;
helper::define_variants! { helper::define_variants! {
day => crate::Day07; day => crate::Day07;
part1 { 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 { 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 { enum HandType {
FiveSame = 0, FiveSame = 0,
FourSame, FourSame,
@ -46,9 +53,9 @@ struct Hand {
bid: u64, bid: u64,
} }
impl HandType { fn hand_type_of(hand: [u8; 5], has_jokers: bool) -> HandType {
fn of(hand: [u8; 5], has_jokers: bool) -> Self { let mut card_type = [0; 5];
let mut map: [Option<(u8, u8)>; 5] = [None; 5]; let mut counts = [0; 5];
let mut jokers = 0; let mut jokers = 0;
for card in hand { for card in hand {
@ -56,37 +63,36 @@ impl HandType {
jokers += 1; jokers += 1;
continue; continue;
} }
if let Some(existing) = map.iter_mut().find(|c| c.is_some_and(|c| c.0 == card)) { if let Some(existing) = card_type.into_iter().position(|c| c == card) {
existing.as_mut().unwrap().1 += 1; counts[existing] += 1;
} else { } else {
let idx = map.iter().position(|c| c.is_none()).unwrap(); let idx = card_type.into_iter().position(|c| c == 0).unwrap();
map[idx] = Some((card, 1)); card_type[idx] = card;
counts[idx] = 1_u8;
} }
} }
map.sort_by_key(|c| cmp::Reverse(c.map(|c| c.1))); counts.sort_by_key(|&c| cmp::Reverse(c));
let map = map.map(|c| c.map_or(0, |c| c.1)); if counts[0] + jokers == 5 {
if map[0] + jokers == 5 { HandType::FiveSame
Self::FiveSame } else if counts[0] + jokers == 4 {
} else if map[0] + jokers == 4 { HandType::FourSame
Self::FourSame
// If there are only cards of two types + jokers, then we can form a full house // If there are only cards of two types + jokers, then we can form a full house
// no matter how they're set up. // no matter how they're set up.
} else if map[0] + map[1] + jokers == 5 { } else if counts[0] + counts[1] + jokers == 5 {
Self::FullHouse HandType::FullHouse
} else if map[0] + jokers == 3 { } else if counts[0] + jokers == 3 {
Self::ThreeSame HandType::ThreeSame
// We need four cards for a two pair. Given that the previous constellations // We need four cards for a two pair. Given that the previous constellations
// are not possible, we are able to build a two pair. // are not possible, we are able to build a two pair.
} else if map[0] + map[1] + jokers == 4 { } else if counts[0] + counts[1] + jokers == 4 {
Self::TwoPair HandType::TwoPair
// Fun fact: This == is fine and needn't be >=, because if map[0]+jokers==3, // Fun fact: This == is fine and needn't be >=, because if map[0]+jokers==3,
// then we can build two pairs. // then we can build two pairs.
} else if map[0] + jokers == 2 { } else if counts[0] + jokers == 2 {
Self::OnePair HandType::OnePair
} else { } else {
Self::HighCard HandType::HighCard
}
} }
} }
@ -96,9 +102,9 @@ mod tests {
#[test] #[test]
fn hand_type() { fn hand_type() {
assert_eq!(HandType::of(*b"32T3K", false), HandType::OnePair); assert_eq!(super::hand_type_of(*b"32T3K", false), HandType::OnePair);
assert_eq!(HandType::of(*b"K6KK6", false), HandType::FullHouse); assert_eq!(super::hand_type_of(*b"K6KK6", false), HandType::FullHouse);
assert_eq!(HandType::of(*b"KTJJT", true), HandType::FourSame); assert_eq!(super::hand_type_of(*b"KTJJT", true), HandType::FourSame);
} }
} }
@ -111,7 +117,7 @@ fn parse(input: &str, has_jokers: bool) -> Vec<Hand> {
let bid = parts.next().unwrap().parse().unwrap(); let bid = parts.next().unwrap().parse().unwrap();
let values = cards.bytes().collect_array::<5>().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 { Hand {
values, values,
@ -174,7 +180,7 @@ helper::tests! {
} }
part2 { part2 {
small => 5905; small => 5905;
default => 0; default => 248781813;
} }
} }
helper::benchmarks! {} helper::benchmarks! {}

111
2023/day07/src/sort_key.rs Normal file
View file

@ -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<Hand> {
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)
}

View file

@ -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<Hand> {
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)
}