Add day 7
This commit is contained in:
1
2023/day7/.gitignore
vendored
Normal file
1
2023/day7/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target/
|
||||
107
2023/day7/Cargo.lock
generated
Normal file
107
2023/day7/Cargo.lock
generated
Normal file
@@ -0,0 +1,107 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "day7"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"indoc",
|
||||
"nom",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||
|
||||
[[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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
12
2023/day7/Cargo.toml
Normal file
12
2023/day7/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "day7"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
indoc = "2.0.4"
|
||||
nom = "7.1.3"
|
||||
strum = "0.25.0"
|
||||
strum_macros = "0.25.3"
|
||||
502
2023/day7/src/main.rs
Normal file
502
2023/day7/src/main.rs
Normal file
@@ -0,0 +1,502 @@
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::{cmp::Ordering, collections::HashMap};
|
||||
|
||||
use nom::{
|
||||
character::complete::{anychar, char, digit1, multispace0, multispace1},
|
||||
combinator::map,
|
||||
multi::{many1, many_m_n, separated_list1},
|
||||
sequence::{delimited, separated_pair},
|
||||
IResult,
|
||||
};
|
||||
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
fn number(i: &str) -> IResult<&str, usize> {
|
||||
map(digit1, |f: &str| f.parse::<usize>().unwrap())(i)
|
||||
}
|
||||
|
||||
fn spaces(i: &str) -> IResult<&str, Vec<char>> {
|
||||
many1(char(' '))(i)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum HandType {
|
||||
HighCard,
|
||||
OnePair,
|
||||
TwoPair,
|
||||
ThreeOfAKind,
|
||||
FullHouse,
|
||||
FourOfAKind,
|
||||
FiveOfAKind,
|
||||
}
|
||||
|
||||
trait Parse {
|
||||
type Out;
|
||||
fn parse(s: &str) -> IResult<&str, Box<Self::Out>>;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
enum CardWithoutJoker {
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
Five,
|
||||
Six,
|
||||
Seven,
|
||||
Eight,
|
||||
Nine,
|
||||
Ten,
|
||||
Jack,
|
||||
Queen,
|
||||
King,
|
||||
Ace,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
|
||||
enum CardWithJoker {
|
||||
Joker,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
Five,
|
||||
Six,
|
||||
Seven,
|
||||
Eight,
|
||||
Nine,
|
||||
Ten,
|
||||
Queen,
|
||||
King,
|
||||
Ace,
|
||||
}
|
||||
|
||||
impl Parse for CardWithoutJoker {
|
||||
type Out = Self;
|
||||
fn parse(s: &str) -> IResult<&str, Box<Self>> {
|
||||
let (rest, c) = anychar(s)?;
|
||||
Ok((
|
||||
rest,
|
||||
Box::new(match c {
|
||||
'2' => Self::Two,
|
||||
'3' => Self::Three,
|
||||
'4' => Self::Four,
|
||||
'5' => Self::Five,
|
||||
'6' => Self::Six,
|
||||
'7' => Self::Seven,
|
||||
'8' => Self::Eight,
|
||||
'9' => Self::Nine,
|
||||
'T' => Self::Ten,
|
||||
'J' => Self::Jack,
|
||||
'Q' => Self::Queen,
|
||||
'K' => Self::King,
|
||||
'A' => Self::Ace,
|
||||
_ => {
|
||||
return Err(nom::Err::Error(nom::error::Error::new(
|
||||
"unknown card",
|
||||
nom::error::ErrorKind::Char,
|
||||
)))
|
||||
}
|
||||
}),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for CardWithJoker {
|
||||
type Out = Self;
|
||||
fn parse(s: &str) -> IResult<&str, Box<Self>> {
|
||||
let (rest, c) = anychar(s)?;
|
||||
Ok((
|
||||
rest,
|
||||
Box::new(match c {
|
||||
'2' => Self::Two,
|
||||
'3' => Self::Three,
|
||||
'4' => Self::Four,
|
||||
'5' => Self::Five,
|
||||
'6' => Self::Six,
|
||||
'7' => Self::Seven,
|
||||
'8' => Self::Eight,
|
||||
'9' => Self::Nine,
|
||||
'T' => Self::Ten,
|
||||
'J' => Self::Joker,
|
||||
'Q' => Self::Queen,
|
||||
'K' => Self::King,
|
||||
'A' => Self::Ace,
|
||||
_ => {
|
||||
return Err(nom::Err::Error(nom::error::Error::new(
|
||||
"unknown card",
|
||||
nom::error::ErrorKind::Char,
|
||||
)))
|
||||
}
|
||||
}),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
trait Hand: PartialEq + Eq + PartialOrd + Ord + ParseHand<Out = Self> + Debug {
|
||||
type CardType: PartialOrd + PartialEq + Eq + Ord + Parse<Out = Self::CardType>;
|
||||
|
||||
fn hand_type(&self) -> HandType;
|
||||
|
||||
fn cards(&self) -> &Vec<Self::CardType>;
|
||||
|
||||
fn from_vec(v: Vec<Self::CardType>) -> Self;
|
||||
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
for (i, card) in self.cards().iter().enumerate() {
|
||||
if *card != other.cards()[i] {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
let (our_hand_type, their_hand_type) = (self.hand_type(), other.hand_type());
|
||||
|
||||
if our_hand_type < their_hand_type {
|
||||
return Some(Ordering::Less);
|
||||
}
|
||||
|
||||
if our_hand_type > their_hand_type {
|
||||
return Some(Ordering::Greater);
|
||||
}
|
||||
|
||||
for (i, card) in self.cards().iter().enumerate() {
|
||||
if *card < other.cards()[i] {
|
||||
return Some(Ordering::Less);
|
||||
}
|
||||
if *card > other.cards()[i] {
|
||||
return Some(Ordering::Greater);
|
||||
}
|
||||
}
|
||||
|
||||
Some(Ordering::Equal)
|
||||
}
|
||||
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
<Self as Hand>::partial_cmp(self, other).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HandWithoutJoker(Vec<CardWithoutJoker>);
|
||||
|
||||
trait ParseHand {
|
||||
type Out: Hand;
|
||||
fn parse(s: &str) -> IResult<&str, Box<Self::Out>> {
|
||||
let (rest, cards) = many_m_n(5, 5, <<Self as ParseHand>::Out as Hand>::CardType::parse)(s)?;
|
||||
|
||||
Ok((
|
||||
rest,
|
||||
Box::new(Self::Out::from_vec(
|
||||
cards
|
||||
.into_iter()
|
||||
.map(|card| *card)
|
||||
.collect::<Vec<<Self::Out as Hand>::CardType>>(),
|
||||
)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseHand for HandWithoutJoker {
|
||||
type Out = Self;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HandWithJoker(Vec<CardWithJoker>);
|
||||
|
||||
impl ParseHand for HandWithJoker {
|
||||
type Out = Self;
|
||||
}
|
||||
|
||||
impl Hand for HandWithoutJoker {
|
||||
type CardType = CardWithoutJoker;
|
||||
|
||||
fn cards(&self) -> &Vec<Self::CardType> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn from_vec(v: Vec<Self::CardType>) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
|
||||
fn hand_type(&self) -> HandType {
|
||||
let mut types: HashMap<Self::CardType, usize> = HashMap::new();
|
||||
self.0.iter().for_each(|card| {
|
||||
types
|
||||
.entry(*card)
|
||||
.and_modify(|count| *count += 1)
|
||||
.or_insert(1);
|
||||
});
|
||||
match types.len() {
|
||||
5 => HandType::HighCard,
|
||||
4 => HandType::OnePair,
|
||||
3 => {
|
||||
let max_count = types.values().max().unwrap();
|
||||
if *max_count == 3 {
|
||||
HandType::ThreeOfAKind
|
||||
} else {
|
||||
HandType::TwoPair
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
let max_count = types.values().max().unwrap();
|
||||
if *max_count == 4 {
|
||||
HandType::FourOfAKind
|
||||
} else {
|
||||
HandType::FullHouse
|
||||
}
|
||||
}
|
||||
1 => HandType::FiveOfAKind,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for HandWithoutJoker {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Hand::eq(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::non_canonical_partial_ord_impl)]
|
||||
impl PartialOrd for HandWithoutJoker {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Hand::partial_cmp(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for HandWithoutJoker {}
|
||||
|
||||
impl Ord for HandWithoutJoker {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
Hand::partial_cmp(self, other).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Hand for HandWithJoker {
|
||||
type CardType = CardWithJoker;
|
||||
|
||||
fn cards(&self) -> &Vec<Self::CardType> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
fn from_vec(v: Vec<Self::CardType>) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
|
||||
fn hand_type(&self) -> HandType {
|
||||
let mut types: HashMap<Self::CardType, usize> = {
|
||||
let mut types = HashMap::new();
|
||||
|
||||
self.cards().iter().for_each(|card| {
|
||||
types
|
||||
.entry(*card)
|
||||
.and_modify(|count| *count += 1)
|
||||
.or_insert(1);
|
||||
});
|
||||
|
||||
types
|
||||
};
|
||||
|
||||
// The joker logic is simple: The best way to improve our hand it to use the jokers as the
|
||||
// same card that we have the *most* of. As jokers substitutions are only used for type
|
||||
// calcuation and not for the value, it does not matter *which* card we pick if there are
|
||||
// multiple with the same number
|
||||
if let Some(joker_count) = types.get(&CardWithJoker::Joker).copied() {
|
||||
if let Some(highest_non_joker_card) = types
|
||||
.iter()
|
||||
.filter(|(card, _count)| **card != CardWithJoker::Joker)
|
||||
.max_by_key(|(_card, count)| *count)
|
||||
.map(|(card, _number)| *card)
|
||||
{
|
||||
*types.get_mut(&highest_non_joker_card).unwrap() += joker_count;
|
||||
types.remove(&CardWithJoker::Joker);
|
||||
} else {
|
||||
// all cards are jokers
|
||||
return HandType::FiveOfAKind;
|
||||
}
|
||||
}
|
||||
|
||||
match types.len() {
|
||||
5 => HandType::HighCard,
|
||||
4 => HandType::OnePair,
|
||||
3 => {
|
||||
let max_count = types.values().max().unwrap();
|
||||
if *max_count == 3 {
|
||||
HandType::ThreeOfAKind
|
||||
} else {
|
||||
HandType::TwoPair
|
||||
}
|
||||
}
|
||||
2 => {
|
||||
let max_count = types.values().max().unwrap();
|
||||
if *max_count == 4 {
|
||||
HandType::FourOfAKind
|
||||
} else {
|
||||
HandType::FullHouse
|
||||
}
|
||||
}
|
||||
1 => HandType::FiveOfAKind,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for HandWithJoker {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Hand::eq(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::non_canonical_partial_ord_impl)]
|
||||
impl PartialOrd for HandWithJoker {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Hand::partial_cmp(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for HandWithJoker {}
|
||||
|
||||
impl Ord for HandWithJoker {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
Hand::partial_cmp(self, other).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HandWithBid<T: Hand<Out = T>> {
|
||||
hand: Box<T>,
|
||||
bid: usize,
|
||||
}
|
||||
|
||||
impl<T: Hand<Out = T>> HandWithBid<T> {
|
||||
fn parse(s: &str) -> IResult<&str, Self> {
|
||||
let (rest, (hand, bid)) = separated_pair(T::parse, spaces, number)(s)?;
|
||||
Ok((
|
||||
rest,
|
||||
Self {
|
||||
hand: Box::new(*hand),
|
||||
bid,
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hand<Out = T>> PartialEq for HandWithBid<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.hand == other.hand
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hand<Out = T>> Eq for HandWithBid<T> {}
|
||||
|
||||
impl<T: Hand<Out = T>> PartialOrd for HandWithBid<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.hand.cmp(&other.hand))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hand<Out = T>> Ord for HandWithBid<T> {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.partial_cmp(other).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Hands<T: Hand<Out = T>>(Vec<HandWithBid<T>>);
|
||||
|
||||
impl<T: Hand<Out = T>> Hands<T> {
|
||||
fn parse(s: &str) -> IResult<&str, Self> {
|
||||
let (rest, hands) = delimited(
|
||||
multispace0,
|
||||
separated_list1(multispace1, HandWithBid::parse),
|
||||
multispace0,
|
||||
)(s)?;
|
||||
|
||||
Ok((rest, Self(hands)))
|
||||
}
|
||||
|
||||
fn sort_by_cards(&mut self) {
|
||||
self.0.sort();
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: &str) -> Result<usize, String> {
|
||||
let (rest, mut hands) = Hands::<HandWithoutJoker>::parse(input).map_err(|e| e.to_string())?;
|
||||
assert!(rest.is_empty());
|
||||
|
||||
hands.sort_by_cards();
|
||||
|
||||
let out = hands
|
||||
.0
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, hand)| (i + 1) * hand.bid)
|
||||
.sum();
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn part2(input: &str) -> Result<usize, String> {
|
||||
let (rest, mut hands) = Hands::<HandWithJoker>::parse(input).map_err(|e| e.to_string())?;
|
||||
assert!(rest.is_empty());
|
||||
|
||||
hands.sort_by_cards();
|
||||
|
||||
let out = hands
|
||||
.0
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, hand)| (i + 1) * hand.bid)
|
||||
.sum();
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
let input = include_str!("../input");
|
||||
|
||||
let args = std::env::args().skip(1).collect::<Vec<String>>();
|
||||
let part = args[0].parse::<usize>().unwrap();
|
||||
|
||||
if part == 1 {
|
||||
println!("Part 1 : {}", part1(input)?);
|
||||
} else if part == 2 {
|
||||
println!("Part 2 : {}", part2(input)?);
|
||||
} else {
|
||||
return Err("unknown part".into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use indoc::indoc;
|
||||
|
||||
#[test]
|
||||
fn example_01() {
|
||||
let input = indoc! {"
|
||||
32T3K 765
|
||||
T55J5 684
|
||||
KK677 28
|
||||
KTJJT 220
|
||||
QQQJA 483
|
||||
"};
|
||||
assert_eq!(part1(input).unwrap(), 6440);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn example_02() {
|
||||
let input = indoc! {"
|
||||
32T3K 765
|
||||
T55J5 684
|
||||
KK677 28
|
||||
KTJJT 220
|
||||
QQQJA 483
|
||||
"};
|
||||
assert_eq!(part2(input).unwrap(), 5905);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user