Add day 7

This commit is contained in:
2023-12-07 10:14:10 +01:00
committed by Hannes Körber
parent 7c021ad810
commit 1e17292b18
4 changed files with 622 additions and 0 deletions

1
2023/day7/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target/

107
2023/day7/Cargo.lock generated Normal file
View 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
View 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
View 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);
}
}