advent-of-code/2023/src/day7.rs

360 lines
9.3 KiB
Rust

use std::{
cmp::Ordering,
collections::{HashMap, HashSet},
};
const INPUT: &str = include_str!("../data/day7.txt");
pub fn day7a() -> String {
// format!("{}", calculate_game(INPUT))
format!("card values changed in part 2")
}
pub fn day7b() -> String {
format!("{}", calculate_game(INPUT))
}
fn calculate_game(game: &str) -> u64 {
let mut hands = game
.lines()
.map(|line| parse_line(line))
.collect::<Vec<(Hand, u64)>>();
hands.sort_by(|(hand1, bid1), (hand2, bid2)| {
if hand1.cmp(hand2) == Ordering::Equal {
bid1.cmp(bid2)
} else {
hand1.cmp(hand2)
}
});
hands
.into_iter()
.enumerate()
.map(|(rank, (_, bid))| (rank as u64 + 1) * bid)
.fold(0, |acc, val| acc + val)
}
fn parse_line(s: &str) -> (Hand, u64) {
let mut parts = s.split(" ");
let hand = parts.next().unwrap();
let bid = parts.next().unwrap();
(Hand::from(hand), bid.parse::<u64>().unwrap())
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Hash, Eq, Ord)]
enum Card {
Joker,
Card(u32),
}
impl From<char> for Card {
fn from(s: char) -> Card {
match s {
'J' => Card::Joker,
'2' => Card::Card(2),
'3' => Card::Card(3),
'4' => Card::Card(4),
'5' => Card::Card(5),
'6' => Card::Card(6),
'7' => Card::Card(7),
'8' => Card::Card(8),
'9' => Card::Card(9),
'T' => Card::Card(10),
'Q' => Card::Card(11),
'K' => Card::Card(12),
'A' => Card::Card(13),
_ => unreachable!(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)]
enum HandType {
HighCard,
OnePair,
TwoPair,
ThreeOfAKind,
FullHouse,
FourOfAKind,
FiveOfAKind,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct Hand(Vec<Card>);
fn remove_highest(set: &mut HashSet<Card>) -> Card {
let mut lst = set.iter().cloned().collect::<Vec<Card>>();
lst.sort();
lst.reverse();
let highest = lst.first().unwrap().clone();
set.remove(&highest);
highest
}
impl Hand {
fn classify(&self) -> HandType {
let mut cards: HashSet<Card> = HashSet::new();
let mut jokers: u32 = 0;
let mut pairs: HashSet<Card> = HashSet::new();
let mut triples: HashSet<Card> = HashSet::new();
let mut quads: HashSet<Card> = HashSet::new();
let mut quints: HashSet<Card> = HashSet::new();
for card in self.0.iter() {
if *card == Card::Joker {
jokers += 1;
} else if quads.contains(card) {
quads.remove(card);
quints.insert(*card);
} else if triples.contains(card) {
triples.remove(card);
quads.insert(*card);
} else if pairs.contains(card) {
pairs.remove(card);
triples.insert(*card);
} else if cards.contains(card) {
cards.remove(card);
pairs.insert(*card);
} else {
cards.insert(*card);
}
}
if jokers == 5 {
return HandType::FiveOfAKind;
}
for _ in 0..jokers {
if quads.len() > 0 {
let highest = remove_highest(&mut quads);
quints.insert(highest);
} else if triples.len() > 0 {
let highest = remove_highest(&mut triples);
quads.insert(highest);
} else if pairs.len() > 0 {
let highest = remove_highest(&mut pairs);
triples.insert(highest);
} else if cards.len() > 0 {
let highest = remove_highest(&mut cards);
pairs.insert(highest);
}
}
if quints.len() > 0 {
HandType::FiveOfAKind
} else if quads.len() > 0 {
HandType::FourOfAKind
} else if triples.len() > 0 && pairs.len() > 0 {
HandType::FullHouse
} else if triples.len() > 0 {
HandType::ThreeOfAKind
} else if pairs.len() == 2 {
HandType::TwoPair
} else if pairs.len() == 1 {
HandType::OnePair
} else {
HandType::HighCard
}
}
}
impl From<&str> for Hand {
fn from(s: &str) -> Hand {
Hand(s.chars().map(|c| Card::from(c)).collect::<Vec<Card>>())
}
}
impl PartialOrd for Hand {
fn partial_cmp(&self, other: &Hand) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Hand {
fn cmp(&self, other: &Hand) -> Ordering {
let class1 = self.classify();
let class2 = other.classify();
if class1 == class2 {
for (card1, card2) in self.0.iter().zip(other.0.clone()) {
let cmp = card1.cmp(&card2);
if cmp != Ordering::Equal {
return cmp;
}
}
return Ordering::Equal;
} else {
class1.cmp(&class2)
}
}
}
#[cfg(test)]
mod test {
use super::*;
const INPUT: &str = "32T3K 765
T55J5 684
KK677 28
KTJJT 220
QQQJA 483
JJJJJ 1";
#[test]
fn it_calculates_test_data() {
assert_eq!(calculate_game(INPUT), 5911);
}
#[test]
fn parses_hand() {
let test_cases = [
(
"32T3K",
Hand(vec![
Card::Card(3),
Card::Card(2),
Card::Card(10),
Card::Card(3),
Card::Card(12),
]),
),
(
"T55J5",
Hand(vec![
Card::Card(10),
Card::Card(5),
Card::Card(5),
Card::Joker,
Card::Card(5),
]),
),
(
"KK677",
Hand(vec![
Card::Card(12),
Card::Card(12),
Card::Card(6),
Card::Card(7),
Card::Card(7),
]),
),
(
"KTJJT",
Hand(vec![
Card::Card(12),
Card::Card(10),
Card::Joker,
Card::Joker,
Card::Card(10),
]),
),
(
"QQQJA",
Hand(vec![
Card::Card(11),
Card::Card(11),
Card::Card(11),
Card::Joker,
Card::Card(13),
]),
),
];
for (hand_str, hand) in test_cases {
assert_eq!(Hand::from(hand_str), hand)
}
}
#[test]
fn classifies_a_hand() {
let test_cases = [
(
Hand(vec![
Card::Card(11),
Card::Joker,
Card::Card(10),
Card::Card(9),
Card::Card(8),
]),
HandType::OnePair,
),
(
Hand(vec![
Card::Card(3),
Card::Card(2),
Card::Card(10),
Card::Card(3),
Card::Card(12),
]),
HandType::OnePair,
),
(
Hand(vec![
Card::Card(10),
Card::Card(5),
Card::Card(5),
Card::Joker,
Card::Card(5),
]),
HandType::FourOfAKind,
),
(
Hand(vec![
Card::Card(12),
Card::Card(12),
Card::Card(6),
Card::Card(7),
Card::Card(7),
]),
HandType::TwoPair,
),
(
Hand(vec![
Card::Card(12),
Card::Joker,
Card::Card(10),
Card::Card(10),
Card::Joker,
]),
HandType::FourOfAKind,
),
(
Hand(vec![
Card::Card(11),
Card::Card(11),
Card::Card(11),
Card::Joker,
Card::Card(13),
]),
HandType::FourOfAKind,
),
(
Hand(vec![
Card::Card(11),
Card::Card(11),
Card::Card(11),
Card::Card(10),
Card::Card(10),
]),
HandType::FullHouse,
),
(
Hand(vec![
Card::Joker,
Card::Joker,
Card::Joker,
Card::Joker,
Card::Joker,
]),
HandType::FiveOfAKind,
),
];
for (hand, classification) in test_cases {
assert_eq!(hand.classify(), classification, "{:?}", hand);
}
}
}