[TASK] Day 7
This commit is contained in:
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -6,6 +6,7 @@ version = 3
|
|||||||
name = "advent-of-code-2023-rust"
|
name = "advent-of-code-2023-rust"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"itertools",
|
||||||
"num",
|
"num",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -15,6 +16,21 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num"
|
name = "num"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
num = "0.4"
|
num = "0.4"
|
||||||
|
itertools = "0.12.0"
|
||||||
|
|
||||||
# For flamegraph, enable this:
|
# For flamegraph, enable this:
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|||||||
1000
input/day07.txt
Normal file
1000
input/day07.txt
Normal file
File diff suppressed because it is too large
Load Diff
5
input/day07_example.txt
Normal file
5
input/day07_example.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
32T3K 765
|
||||||
|
T55J5 684
|
||||||
|
KK677 28
|
||||||
|
KTJJT 220
|
||||||
|
QQQJA 483
|
||||||
199
src/day7.rs
Normal file
199
src/day7.rs
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
use std::{fmt::Display, cmp::Ordering};
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use crate::day_solver::DaySolver;
|
||||||
|
#[cfg(test)]
|
||||||
|
use crate::util::read_file;
|
||||||
|
|
||||||
|
const CARD_ORDER: [char; 13] = ['A', 'K', 'Q', 'J', 'T', '9', '8', '7', '6', '5', '4', '3', '2'];
|
||||||
|
|
||||||
|
pub struct Day7 {
|
||||||
|
hands: Vec<Hand>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Hand {
|
||||||
|
cards: [char; 5],
|
||||||
|
bid: usize
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Hand {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{} {}", self.cards.iter().collect::<String>(), self.bid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hand {
|
||||||
|
fn parse(input: &str) -> Self {
|
||||||
|
let (cards_str, bid_str) = input.split_once(" ").unwrap();
|
||||||
|
return Hand {
|
||||||
|
cards: cards_str.chars().collect::<Vec<char>>().try_into().unwrap(),
|
||||||
|
bid: bid_str.parse().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_type(&self) -> u8 {
|
||||||
|
let mut found_three_of_kind = false;
|
||||||
|
let mut found_pair = false;
|
||||||
|
for unique_card in self.cards.iter().unique() {
|
||||||
|
let card_appearances = self.cards.iter().filter(|c| c == &unique_card).count();
|
||||||
|
if card_appearances == 5 {
|
||||||
|
return FIVE_OF_A_KIND;
|
||||||
|
} else if card_appearances == 4 {
|
||||||
|
return FOUR_OF_A_KIND;
|
||||||
|
} else if card_appearances == 3 {
|
||||||
|
found_three_of_kind = true;
|
||||||
|
} else if card_appearances == 2 {
|
||||||
|
if found_pair {
|
||||||
|
return TWO_PAIR
|
||||||
|
} else {
|
||||||
|
found_pair = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found_three_of_kind && found_pair {
|
||||||
|
return FULL_HOUSE;
|
||||||
|
} else if found_three_of_kind {
|
||||||
|
return THREE_OF_A_KIND;
|
||||||
|
} else if found_pair {
|
||||||
|
return ONE_PAIR;
|
||||||
|
}
|
||||||
|
return HIGH_CARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates the maximum type, given that "J" is a joker
|
||||||
|
fn calc_max_type(&self) -> u8 {
|
||||||
|
if !self.cards.contains(&'J') {
|
||||||
|
return self.calc_type();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut max_type = self.calc_type();
|
||||||
|
if max_type == FIVE_OF_A_KIND { return max_type };
|
||||||
|
|
||||||
|
// It only makes sense to try to map jokers to any of the other cards we have
|
||||||
|
let owned_cards: Vec<&char> = self.cards.iter().filter(|c| c != &&'J').unique().collect();
|
||||||
|
for i in 0..5 {
|
||||||
|
if self.cards[i] == 'J' {
|
||||||
|
for c in &owned_cards {
|
||||||
|
let mut new_cards = self.cards.clone();
|
||||||
|
new_cards[i] = *c.to_owned();
|
||||||
|
let new_hand = Hand { cards: new_cards, bid: 0};
|
||||||
|
let j_type = new_hand.calc_max_type();
|
||||||
|
// println!("Trying hand {} as {}, got type {}", self, new_hand, j_type);
|
||||||
|
if j_type > max_type {
|
||||||
|
max_type = j_type;
|
||||||
|
if max_type == FIVE_OF_A_KIND { return max_type }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return max_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const FIVE_OF_A_KIND: u8 = 64;
|
||||||
|
const FOUR_OF_A_KIND: u8 = 32;
|
||||||
|
const FULL_HOUSE: u8 = 16;
|
||||||
|
const THREE_OF_A_KIND: u8 = 8;
|
||||||
|
const TWO_PAIR: u8 = 4;
|
||||||
|
const ONE_PAIR: u8 = 2;
|
||||||
|
const HIGH_CARD: u8 = 0;
|
||||||
|
|
||||||
|
impl Day7 {
|
||||||
|
|
||||||
|
pub fn create(input: String) -> Self {
|
||||||
|
// let lines = input.lines();
|
||||||
|
|
||||||
|
// Put the input into the day struct
|
||||||
|
return Day7 {
|
||||||
|
hands: input.lines().map(Hand::parse).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn card_rank(card: &char) -> usize {
|
||||||
|
for i in 0..CARD_ORDER.len() {
|
||||||
|
if &CARD_ORDER[i] == card {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CARD_ORDER.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_card(a: &char, b: &char) -> Ordering {
|
||||||
|
return card_rank(b).partial_cmp(&card_rank(a)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_card_pt2(a: &char, b: &char) -> Ordering {
|
||||||
|
if a == &'J' && b == &'J' { return Ordering::Equal }
|
||||||
|
else if a == &'J' { return Ordering::Less }
|
||||||
|
else if b == &'J' { return Ordering::Greater }
|
||||||
|
else {
|
||||||
|
return card_rank(b).partial_cmp(&card_rank(a)).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DaySolver for Day7 {
|
||||||
|
|
||||||
|
|
||||||
|
fn solve_part1(&mut self) -> String {
|
||||||
|
let mut hands_and_types: Vec<(&Hand, u8)> = self.hands.iter()
|
||||||
|
.map(|h| (h, h.calc_type()))
|
||||||
|
.collect();
|
||||||
|
hands_and_types.sort_by(|a, b| {
|
||||||
|
if a.1 != b.1 {
|
||||||
|
return a.1.partial_cmp(&b.1).unwrap();
|
||||||
|
} else {
|
||||||
|
for i in 0usize..5 {
|
||||||
|
let card_comp = compare_card(&a.0.cards[i], &b.0.cards[i]);
|
||||||
|
if card_comp.is_ne() {
|
||||||
|
return card_comp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ordering::Equal;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return hands_and_types.iter().map(|(h,_)|h).enumerate()
|
||||||
|
.map(|(r, h)| (r + 1) * h.bid)
|
||||||
|
.sum::<usize>()
|
||||||
|
.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solve_part2(&mut self) -> String {
|
||||||
|
let mut hands_and_max_types: Vec<(&Hand, u8)> = self.hands.iter()
|
||||||
|
.map(|h| (h, h.calc_max_type()))
|
||||||
|
.collect();
|
||||||
|
hands_and_max_types.sort_by(|a, b| {
|
||||||
|
if a.1 != b.1 {
|
||||||
|
return a.1.partial_cmp(&b.1).unwrap();
|
||||||
|
} else {
|
||||||
|
for i in 0..5 {
|
||||||
|
let card_comp = compare_card_pt2(&a.0.cards[i], &b.0.cards[i]);
|
||||||
|
if card_comp.is_ne() {
|
||||||
|
return card_comp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ordering::Equal;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// for (hand, max_type) in &hands_and_max_types {
|
||||||
|
// println!("Hand {} with type {}", hand, max_type);
|
||||||
|
// }
|
||||||
|
return hands_and_max_types.iter().map(|(h,_)|h).enumerate()
|
||||||
|
.map(|(r, h)| (r + 1) * h.bid)
|
||||||
|
.sum::<usize>()
|
||||||
|
.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_part1() {
|
||||||
|
let mut day = Day7::create(read_file("input/day07_example.txt"));
|
||||||
|
assert_eq!("6440", day.solve_part1());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_part2() {
|
||||||
|
let mut day = Day7::create(read_file("input/day07_example.txt"));
|
||||||
|
assert_eq!("5905", day.solve_part2());
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ use crate::day3::Day3;
|
|||||||
use crate::day4::Day4;
|
use crate::day4::Day4;
|
||||||
use crate::day5::Day5;
|
use crate::day5::Day5;
|
||||||
use crate::day6::Day6;
|
use crate::day6::Day6;
|
||||||
|
use crate::day7::Day7;
|
||||||
use crate::day_solver::DaySolver;
|
use crate::day_solver::DaySolver;
|
||||||
use crate::util::read_file;
|
use crate::util::read_file;
|
||||||
|
|
||||||
@@ -17,9 +18,10 @@ mod day3;
|
|||||||
mod day4;
|
mod day4;
|
||||||
mod day5;
|
mod day5;
|
||||||
mod day6;
|
mod day6;
|
||||||
|
mod day7;
|
||||||
mod day_solver;
|
mod day_solver;
|
||||||
|
|
||||||
const MAX_DAY: u8 = 6;
|
const MAX_DAY: u8 = 7;
|
||||||
const DEFAULT_BENCHMARK_AMOUNT: u32 = 100;
|
const DEFAULT_BENCHMARK_AMOUNT: u32 = 100;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -96,6 +98,7 @@ fn build_day_solver(day: u8, input: String) -> Option<Box<dyn DaySolver>> {
|
|||||||
4 => Some(Box::new(Day4::create(input))),
|
4 => Some(Box::new(Day4::create(input))),
|
||||||
5 => Some(Box::new(Day5::create(input))),
|
5 => Some(Box::new(Day5::create(input))),
|
||||||
6 => Some(Box::new(Day6::create(input))),
|
6 => Some(Box::new(Day6::create(input))),
|
||||||
|
7 => Some(Box::new(Day7::create(input))),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user