352 lines
9.5 KiB
Rust
352 lines
9.5 KiB
Rust
advent_of_code::solution!(7);
|
|
|
|
use std::cmp::Ordering;
|
|
use std::collections::{BTreeMap, HashSet};
|
|
use std::str::FromStr;
|
|
|
|
#[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
enum Card {
|
|
A,
|
|
K,
|
|
Q,
|
|
J,
|
|
T,
|
|
C9,
|
|
C8,
|
|
C7,
|
|
C6,
|
|
C5,
|
|
C4,
|
|
C3,
|
|
C2,
|
|
}
|
|
|
|
#[derive(Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
|
enum Part2Card {
|
|
A,
|
|
K,
|
|
Q,
|
|
T,
|
|
C9,
|
|
C8,
|
|
C7,
|
|
C6,
|
|
C5,
|
|
C4,
|
|
C3,
|
|
C2,
|
|
J,
|
|
}
|
|
|
|
fn card_to_part2(input: &Card) -> Part2Card {
|
|
match input {
|
|
Card::A => Part2Card::A,
|
|
Card::K => Part2Card::K,
|
|
Card::Q => Part2Card::Q,
|
|
Card::T => Part2Card::T,
|
|
Card::C9 => Part2Card::C9,
|
|
Card::C8 => Part2Card::C8,
|
|
Card::C7 => Part2Card::C7,
|
|
Card::C6 => Part2Card::C6,
|
|
Card::C5 => Part2Card::C5,
|
|
Card::C4 => Part2Card::C4,
|
|
Card::C3 => Part2Card::C3,
|
|
Card::C2 => Part2Card::C2,
|
|
Card::J => Part2Card::J,
|
|
}
|
|
}
|
|
|
|
impl FromStr for Card {
|
|
type Err = ();
|
|
fn from_str(input: &str) -> Result<Card, Self::Err> {
|
|
match input {
|
|
"A" => Ok(Card::A),
|
|
"K" => Ok(Card::K),
|
|
"Q" => Ok(Card::Q),
|
|
"J" => Ok(Card::J),
|
|
"T" => Ok(Card::T),
|
|
"9" => Ok(Card::C9),
|
|
"8" => Ok(Card::C8),
|
|
"7" => Ok(Card::C7),
|
|
"6" => Ok(Card::C6),
|
|
"5" => Ok(Card::C5),
|
|
"4" => Ok(Card::C4),
|
|
"3" => Ok(Card::C3),
|
|
"2" => Ok(Card::C2),
|
|
_ => Err(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
|
|
enum HandKinds {
|
|
FiveAlike,
|
|
FourAlike,
|
|
FullHouse,
|
|
ThreeAlike,
|
|
TwoPair,
|
|
OnePair,
|
|
HighCard,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct Hand {
|
|
bid: u64,
|
|
cards: [Card; 5],
|
|
hand_kind: HandKinds,
|
|
}
|
|
|
|
impl PartialEq for Hand {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.hand_kind == other.hand_kind && self.cards == other.cards
|
|
}
|
|
}
|
|
|
|
impl Eq for Hand {}
|
|
|
|
impl PartialOrd for Hand {
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
Some(self.cmp(other))
|
|
}
|
|
}
|
|
|
|
impl Ord for Hand {
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
if self.hand_kind == other.hand_kind {
|
|
if self.cards[0] != other.cards[0] {
|
|
self.cards[0].cmp(&other.cards[0])
|
|
} else if self.cards[1] != other.cards[1] {
|
|
self.cards[1].cmp(&other.cards[1])
|
|
} else if self.cards[2] != other.cards[2] {
|
|
self.cards[2].cmp(&other.cards[2])
|
|
} else if self.cards[3] != other.cards[3] {
|
|
self.cards[3].cmp(&other.cards[3])
|
|
} else if self.cards[4] != other.cards[4] {
|
|
self.cards[4].cmp(&other.cards[4])
|
|
} else {
|
|
panic!("could not sort hand")
|
|
}
|
|
} else {
|
|
self.hand_kind.cmp(&other.hand_kind)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PartialEq for Part2Hand {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.hand_kind == other.hand_kind && self.cards == other.cards
|
|
}
|
|
}
|
|
|
|
impl Eq for Part2Hand {}
|
|
|
|
impl PartialOrd for Part2Hand {
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
Some(self.cmp(other))
|
|
}
|
|
}
|
|
|
|
impl Ord for Part2Hand {
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
if self.hand_kind == other.hand_kind {
|
|
if self.cards[0] != other.cards[0] {
|
|
self.cards[0].cmp(&other.cards[0])
|
|
} else if self.cards[1] != other.cards[1] {
|
|
self.cards[1].cmp(&other.cards[1])
|
|
} else if self.cards[2] != other.cards[2] {
|
|
self.cards[2].cmp(&other.cards[2])
|
|
} else if self.cards[3] != other.cards[3] {
|
|
self.cards[3].cmp(&other.cards[3])
|
|
} else if self.cards[4] != other.cards[4] {
|
|
self.cards[4].cmp(&other.cards[4])
|
|
} else {
|
|
panic!("could not sort hand")
|
|
}
|
|
} else {
|
|
self.hand_kind.cmp(&other.hand_kind)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn get_hand_type<T>(cards: &[T; 5]) -> HandKinds
|
|
where
|
|
T: Eq + PartialEq + PartialOrd + std::hash::Hash,
|
|
{
|
|
let mut distinct_cards: HashSet<&T> = HashSet::new();
|
|
for c in cards.iter() {
|
|
distinct_cards.insert(c);
|
|
}
|
|
match distinct_cards.len() {
|
|
1 => HandKinds::FiveAlike,
|
|
2 => {
|
|
let tcount = cards
|
|
.iter()
|
|
.fold(0, |acc, c| if *c == cards[0] { acc + 1 } else { acc });
|
|
if tcount == 4 || tcount == 1 {
|
|
HandKinds::FourAlike
|
|
} else {
|
|
HandKinds::FullHouse
|
|
}
|
|
}
|
|
3 => {
|
|
let mut counts = distinct_cards
|
|
.iter()
|
|
.map(|card| {
|
|
cards
|
|
.iter()
|
|
.fold(0, |acc, c| if c == *card { acc + 1 } else { acc })
|
|
})
|
|
.collect::<Vec<u8>>();
|
|
counts.sort();
|
|
if counts == [1, 1, 3] {
|
|
HandKinds::ThreeAlike
|
|
} else {
|
|
HandKinds::TwoPair
|
|
}
|
|
}
|
|
4 => HandKinds::OnePair,
|
|
5 => HandKinds::HighCard,
|
|
_ => panic!(
|
|
"expected to find size 1-5, but instead got {}",
|
|
distinct_cards.len()
|
|
),
|
|
}
|
|
}
|
|
|
|
struct Part2Hand {
|
|
bid: u64,
|
|
cards: [Part2Card; 5],
|
|
hand_kind: HandKinds,
|
|
}
|
|
|
|
fn parse(input: &str) -> Vec<Hand> {
|
|
input
|
|
.lines()
|
|
.filter_map(|line| {
|
|
if !line.is_empty() {
|
|
let parts = line.split(" ").collect::<Vec<&str>>();
|
|
let cards = TryInto::<[Card; 5]>::try_into(
|
|
parts[0]
|
|
.split("")
|
|
.filter_map(|c| {
|
|
if c.is_empty() {
|
|
None
|
|
} else {
|
|
Some(Card::from_str(c).unwrap())
|
|
}
|
|
})
|
|
.collect::<Vec<Card>>(),
|
|
)
|
|
.unwrap_or_else(|v: Vec<Card>| {
|
|
panic!("Expected a Vec of length 5 but it was {}", v.len())
|
|
});
|
|
let bid = parts[1].parse::<u64>().unwrap();
|
|
let hand_kind = get_hand_type(&cards);
|
|
Some(Hand {
|
|
bid,
|
|
cards,
|
|
hand_kind,
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
pub fn part_one(input: &str) -> Option<u32> {
|
|
let mut sum: u64 = 0;
|
|
let mut hands = parse(input)
|
|
.iter()
|
|
.map(|h| h.clone())
|
|
.collect::<Vec<Hand>>();
|
|
hands.sort();
|
|
for (index, hand) in hands.iter().rev().enumerate() {
|
|
sum += hand.bid * (index as u64 + 1);
|
|
}
|
|
Some(sum.try_into().unwrap())
|
|
}
|
|
|
|
pub fn part_two(input: &str) -> Option<u32> {
|
|
let mut sum: u64 = 0;
|
|
let mut hands = parse(input)
|
|
.iter()
|
|
.map(|h| {
|
|
let bid = h.bid;
|
|
let p2cards = h
|
|
.cards
|
|
.iter()
|
|
.map(|c| card_to_part2(c))
|
|
.collect::<Vec<Part2Card>>();
|
|
let mut counts: BTreeMap<&Part2Card, u32> = BTreeMap::new();
|
|
p2cards.iter().for_each(|c| {
|
|
if !counts.contains_key(c) {
|
|
counts.insert(c, 0);
|
|
}
|
|
let ccount = counts.get_mut(c).unwrap();
|
|
*ccount = *ccount + 1;
|
|
});
|
|
|
|
let mut highest_count = 0;
|
|
let mut highest_card = &Part2Card::J;
|
|
for (card, count) in counts.iter() {
|
|
if **card != Part2Card::J {
|
|
if *count > highest_count {
|
|
highest_count = *count;
|
|
highest_card = card;
|
|
} else if *count == highest_count && *card > highest_card {
|
|
highest_card = card;
|
|
}
|
|
}
|
|
}
|
|
let newhand = TryInto::<[Part2Card; 5]>::try_into(
|
|
p2cards
|
|
.iter()
|
|
.map(|c| {
|
|
if *c == Part2Card::J {
|
|
*highest_card
|
|
} else {
|
|
*c
|
|
}
|
|
})
|
|
.collect::<Vec<Part2Card>>(),
|
|
)
|
|
.unwrap_or_else(|v: Vec<Part2Card>| {
|
|
panic!("Expected a Vec of length 5 but it was {}", v.len())
|
|
});
|
|
let hand_kind = get_hand_type(&newhand);
|
|
let cards =
|
|
TryInto::<[Part2Card; 5]>::try_into(p2cards).unwrap_or_else(|v: Vec<Part2Card>| {
|
|
panic!("Expected a Vec of length 5 but it was {}", v.len())
|
|
});
|
|
Part2Hand {
|
|
bid,
|
|
cards,
|
|
hand_kind,
|
|
}
|
|
})
|
|
.collect::<Vec<Part2Hand>>();
|
|
hands.sort();
|
|
for (index, hand) in hands.iter().rev().enumerate() {
|
|
sum += hand.bid * (index as u64 + 1);
|
|
}
|
|
Some(sum.try_into().unwrap())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_part_one() {
|
|
let result = part_one(&advent_of_code::template::read_file("examples", DAY));
|
|
assert_eq!(result, Some(6440));
|
|
}
|
|
|
|
#[test]
|
|
fn test_part_two() {
|
|
let result = part_two(&advent_of_code::template::read_file("examples", DAY));
|
|
assert_eq!(result, Some(5905));
|
|
}
|
|
}
|