From 2d93f5f74817d1ccc34eb8503c20c83c92bc988f Mon Sep 17 00:00:00 2001 From: Xiretza Date: Thu, 7 Dec 2023 17:37:24 +0000 Subject: [PATCH] 2023 day7/rust: add solution --- 2023/day7/rust/Cargo.toml | 11 ++ 2023/day7/rust/src/main.rs | 227 +++++++++++++++++++++++++++++++++++++ Cargo.lock | 54 +++++++-- Cargo.toml | 1 + 4 files changed, 286 insertions(+), 7 deletions(-) create mode 100644 2023/day7/rust/Cargo.toml create mode 100644 2023/day7/rust/src/main.rs diff --git a/2023/day7/rust/Cargo.toml b/2023/day7/rust/Cargo.toml new file mode 100644 index 0000000..ac7c15a --- /dev/null +++ b/2023/day7/rust/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "rust_2023_07" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +aoc = { path = "../../../common/rust" } +itertools = "0.12.0" +strum = { version = "0.25.0", features = ["derive"] } diff --git a/2023/day7/rust/src/main.rs b/2023/day7/rust/src/main.rs new file mode 100644 index 0000000..0873360 --- /dev/null +++ b/2023/day7/rust/src/main.rs @@ -0,0 +1,227 @@ +#![warn(clippy::pedantic)] + +use std::{cmp::Ordering, io::stdin}; + +use itertools::Itertools; +use strum::{EnumIter, IntoEnumIterator}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, EnumIter)] +enum Card { + N2, + N3, + N4, + N5, + N6, + N7, + N8, + N9, + T, + J, + Q, + K, + A, +} + +impl Card { + fn parse(c: char) -> Self { + match c { + '2' => Card::N2, + '3' => Card::N3, + '4' => Card::N4, + '5' => Card::N5, + '6' => Card::N6, + '7' => Card::N7, + '8' => Card::N8, + '9' => Card::N9, + 'T' => Card::T, + 'J' => Card::J, + 'Q' => Card::Q, + 'K' => Card::K, + 'A' => Card::A, + _ => unreachable!(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +enum Type { + HighCard, + OnePair, + TwoPairs, + ThreeSame, + FullHouse, + FourSame, + FiveSame, +} + +impl Type { + fn classify_inner(mut value: [Card; 5]) -> Option { + value.sort(); + let groups: Vec<_> = value + .into_iter() + .group_by(|c| *c) + .into_iter() + .map(|(_card, group)| group.count()) + .collect(); + + if groups.contains(&5) { + Some(Type::FiveSame) + } else if groups.contains(&4) { + Some(Type::FourSame) + } else if groups.contains(&3) { + if groups.contains(&2) { + Some(Type::FullHouse) + } else { + Some(Type::ThreeSame) + } + } else if groups.contains(&2) { + if groups.iter().filter(|&&n| n == 2).count() == 2 { + Some(Type::TwoPairs) + } else { + Some(Type::OnePair) + } + } else if groups.iter().all(|&n| n == 1) { + Some(Type::HighCard) + } else { + None + } + } + + fn classify(value: [Card; 5], mode: Mode) -> Option { + match mode { + Mode::Jokers => { + if !value.contains(&Card::J) { + return Self::classify_inner(value); + } + + let jokers: Vec<_> = value.iter().positions(|&c| c == Card::J).collect(); + + let all_other_cards = Card::iter().filter(|&c| c != Card::J); + std::iter::repeat(all_other_cards) + .take(jokers.len()) + .multi_cartesian_product() + .map(|replacements| { + let mut value = value; + for (&pos, replacement) in jokers.iter().zip(replacements) { + value[pos] = replacement; + } + value + }) + .map(Type::classify_inner) + .max() + .unwrap() + } + Mode::NoJokers => Self::classify_inner(value), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Mode { + Jokers, + NoJokers, +} + +#[derive(Debug, Clone, Copy)] +struct Hand { + typ: Option, + cards: [Card; 5], + + mode: Mode, + bid: usize, +} + +impl Hand { + pub fn parse(s: &str, mode: Mode) -> Self { + let (cards, bid) = s.split_once(' ').unwrap(); + let bid = bid.parse().unwrap(); + let cards: Vec<_> = cards.chars().map(Card::parse).collect(); + let cards = *<&[Card; 5]>::try_from(cards.as_slice()).unwrap(); + let typ = Type::classify(cards, mode); + + Self { + typ, + cards, + mode, + bid, + } + } + + pub fn set_mode(&mut self, mode: Mode) { + if mode == self.mode { + return; + } + self.typ = Type::classify(self.cards, mode); + self.mode = mode; + } +} + +impl PartialEq for Hand { + fn eq(&self, other: &Self) -> bool { + assert!( + self.mode == other.mode, + "can't compare hands with different modes" + ); + self.cards == other.cards + } +} + +impl Eq for Hand {} + +impl Ord for Hand { + fn cmp(&self, other: &Self) -> Ordering { + assert!( + self.mode == other.mode, + "can't compare hands with different modes" + ); + + self.typ.cmp(&other.typ).then_with(|| { + self.cards + .iter() + .zip(&other.cards) + .map(|(s, o)| { + if let Mode::Jokers = self.mode { + match (s, o) { + (Card::J, Card::J) => {} + (Card::J, _) => return Ordering::Less, + (_, Card::J) => return Ordering::Greater, + _ => {} + } + } + + s.cmp(o) + }) + .reduce(Ordering::then) + .unwrap() + }) + } +} + +impl PartialOrd for Hand { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +fn get_winnings(hands: &mut [Hand], mode: Mode) -> usize { + for hand in hands.iter_mut() { + hand.set_mode(mode); + } + hands.sort(); + hands + .iter() + .enumerate() + .map(|(rank, hand)| hand.bid * (rank + 1)) + .sum() +} + +fn main() { + let mut hands: Vec<_> = stdin() + .lines() + .map(Result::unwrap) + .map(|s| Hand::parse(&s, Mode::NoJokers)) + .collect(); + + println!("{}", get_winnings(&mut hands, Mode::NoJokers)); + println!("{}", get_winnings(&mut hands, Mode::Jokers)); +} diff --git a/Cargo.lock b/Cargo.lock index 31f5a74..03e1348 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,7 +16,7 @@ name = "aoc" version = "0.1.0" dependencies = [ "num-traits", - "strum", + "strum 0.24.1", "thiserror", ] @@ -89,7 +89,7 @@ dependencies = [ "clap", "criterion-plot", "csv", - "itertools", + "itertools 0.10.5", "lazy_static", "num-traits", "oorandom", @@ -111,7 +111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -252,6 +252,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -452,7 +461,7 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" name = "rust_2021_1" version = "0.1.0" dependencies = [ - "itertools", + "itertools 0.10.5", ] [[package]] @@ -596,7 +605,7 @@ name = "rust_2022_14" version = "0.1.0" dependencies = [ "aoc", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -612,7 +621,7 @@ version = "0.1.0" dependencies = [ "aoc", "petgraph", - "strum", + "strum 0.24.1", ] [[package]] @@ -651,6 +660,15 @@ dependencies = [ "aoc", ] +[[package]] +name = "rust_2023_07" +version = "0.1.0" +dependencies = [ + "aoc", + "itertools 0.12.0", + "strum 0.25.0", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -725,7 +743,16 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ - "strum_macros", + "strum_macros 0.24.3", +] + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros 0.25.3", ] [[package]] @@ -741,6 +768,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.39", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/Cargo.toml b/Cargo.toml index 0885520..1e30df2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,4 +32,5 @@ members = [ "2023/day4/rust", "2023/day5/rust", "2023/day6/rust", + "2023/day7/rust", ]