[TASK] Finally(!) completed day 12

This commit is contained in:
2023-12-22 22:08:08 +01:00
parent 779df41294
commit cb0fe825f4
5 changed files with 1284 additions and 2 deletions

1000
input/day12.txt Normal file

File diff suppressed because it is too large Load Diff

6
input/day12_example.txt Normal file
View File

@@ -0,0 +1,6 @@
???.### 1,1,3
.??..??...?##. 1,1,3
?#?#?#?#?#?#?#? 1,3,1,6
????.#...#... 4,1,1
????.######..#####. 1,6,5
?###???????? 3,2,1

View File

@@ -66,6 +66,6 @@ fn test_part1() {
#[test]
fn test_part2() {
let mut day = Day11::create(read_file("input/day11_example.txt"));
let day = Day11::create(read_file("input/day11_example.txt"));
assert_eq!(8410, day.expand_galaxies_and_calculate_distances(100));
}

274
src/day12.rs Normal file
View File

@@ -0,0 +1,274 @@
use std::fmt::Display;
use itertools::Itertools;
use crate::{day_solver::DaySolver, util::Grid};
#[cfg(test)]
use crate::util::read_file;
pub struct Day12 {
lines: Vec<GearLine>
}
struct GearLine {
gears: Vec<char>,
sizes: Vec<usize>
}
#[derive(Debug, Clone, Copy)]
struct ArrangementStart {
gear_at: usize,
size_at: usize
}
impl ArrangementStart {
fn new(gear_at: usize, size_at: usize) -> Self {
ArrangementStart { gear_at, size_at }
}
}
impl Display for ArrangementStart {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return write!(f, "{} {}", self.gear_at, self.size_at)
}
}
impl Display for GearLine {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return write!(f, "{} {}", self.gears.iter().join(""), self.sizes.iter().map(|x| x.to_string()).join(","))
}
}
enum NextStep {
Invalid,
TryBoth,
ValidSingle,
Move(ArrangementStart),
}
impl Display for NextStep {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return match self {
Self::Invalid => write!(f, "Invalid"),
Self::TryBoth => write!(f, "Branch"),
Self::ValidSingle => write!(f, "Valid"),
Self::Move(res) => write!(f, "{}", res)
}
}
}
struct Cache {
assume_gear_cache: Grid<Option<usize>>,
assume_dot_cache: Grid<Option<usize>>,
assume_none_cache: Grid<Option<usize>>
}
impl Cache {
fn new(line: &GearLine) -> Self {
Cache {
assume_dot_cache: Grid { data: vec![None; (line.gears.len() + 1) * (line.sizes.len() + 1)], default: Some(None), width: (line.gears.len() + 1) },
assume_gear_cache: Grid { data: vec![None; (line.gears.len() + 1) * (line.sizes.len() + 1)], default: Some(None), width: (line.gears.len() + 1) },
assume_none_cache: Grid { data: vec![None; (line.gears.len() + 1) * (line.sizes.len() + 1)], default: Some(None), width: (line.gears.len() + 1) }
}
}
fn get(&self, start: &ArrangementStart, assume: &Option<char>) -> Option<usize> {
return match assume {
None => self.assume_none_cache.get(&start.gear_at, &start.size_at).to_owned(),
Some('.') => self.assume_dot_cache.get(&start.gear_at, &start.size_at).to_owned(),
Some('#') => self.assume_gear_cache.get(&start.gear_at, &start.size_at).to_owned(),
Some(_) => panic!("wtf?")
}
}
fn set(&mut self, start: &ArrangementStart, assume: &Option<char>, res: usize) {
match assume {
None => self.assume_none_cache.set(&start.gear_at, &start.size_at, Some(res)),
Some('.') => self.assume_dot_cache.set(&start.gear_at, &start.size_at, Some(res)),
Some('#') => self.assume_gear_cache.set(&start.gear_at, &start.size_at, Some(res)),
Some(_) => panic!("Set wtf?")
}
}
}
impl GearLine {
fn parse(line: &str) -> Self {
// Example: ???.### 1,1,3
let (gears, sizes) = line.split_once(" ").unwrap();
return GearLine {
gears: gears.chars().collect_vec(),
sizes: sizes.split(",").map(|s| s.parse().unwrap()).collect()
}
}
// fn is_valid(&self) -> bool {
// if self.sizes.len() == 0 && self.gears.iter().all(|c| *c == '.') {
// return true;
// }
// let mut i = 0;
// let mut cur_group_size = 0;
// for c in &self.gears {
// if *c == '?' {
// panic!("Can only validate final lines, but got {}", self.gears.iter().join(""))
// }
// if *c == '#' {
// cur_group_size += 1;
// } else if *c == '.' {
// if cur_group_size > 0 {
// if i >= self.sizes.len() || self.sizes[i] != cur_group_size {
// return false
// }
// i += 1;
// }
// cur_group_size = 0;
// }
// }
// if cur_group_size > 0 {
// return i + 1 == self.sizes.len() && self.sizes[i] == cur_group_size
// } else {
// return i == self.sizes.len();
// }
// }
fn move_forward(&self, start: ArrangementStart, assume_current: Option<char>) -> NextStep {
let sizes_left = self.sizes.len() - start.size_at;
let gears_left = self.gears.len() - start.gear_at;
if gears_left == 0 {
return if sizes_left == 0 { NextStep::ValidSingle } else { NextStep::Invalid };
}
// If the line starts or end with a '#' or '.' we can shave that off,
// modifying the original gears line
match assume_current.unwrap_or(self.gears[start.gear_at]) {
'#' => {
// We know that the first of the "sizes" starts at position 0, so we can just remove that part:
if sizes_left == 0 {
// This isn't even valid, but we also can't shrink anymore
return NextStep::Invalid;
}
let cur_size = self.sizes[start.size_at];
if !(self.gears.iter().skip(start.gear_at).take(cur_size).all(|c| *c == '#' || *c == '?')) {
// Not valid
return NextStep::Invalid;
}
if gears_left == cur_size {
return if sizes_left == 1 { NextStep::ValidSingle } else { NextStep::Invalid };
} else if gears_left > cur_size && self.gears[start.gear_at + cur_size] != '#' {
NextStep::Move(ArrangementStart::new(start.gear_at + cur_size + 1, start.size_at + 1))
} else {
return NextStep::Invalid;
}
}
'.' => {
// If we start with a '.', this part of the sequence can just be ignored
for i in start.gear_at..self.gears.len() {
if self.gears[i] == '#' || self.gears[i] == '?' {
return NextStep::Move(ArrangementStart { gear_at: i.max(start.gear_at + 1), size_at: start.size_at })
}
}
// There's only '.' left
return if sizes_left == 0 { NextStep::ValidSingle } else { NextStep::Invalid }
}
_ => NextStep::TryBoth
}
}
fn find_arrangements_recursive(&self, start: ArrangementStart, assume_current: Option<char>, cache: &mut Cache) -> usize {
if let Some(res) = cache.get(&start, &assume_current) {
return res.to_owned();
}
let next_step = self.move_forward(start, assume_current);
// println!("Next step on {} (start {}, '{}') => {}", self, start, assume_current.unwrap_or('_'), next_step);
let res = match next_step {
NextStep::Invalid => 0,
NextStep::ValidSingle => 1,
NextStep::Move(next_start) => self.find_arrangements_recursive(next_start, None, cache),
NextStep::TryBoth => {
return self.find_arrangements_recursive(start, Some('#'), cache)
+ self.find_arrangements_recursive(start, Some('.'), cache)
}
};
cache.set(&start, &assume_current, res);
return res;
}
fn find_arrangements(&self) -> usize {
return self.find_arrangements_recursive(ArrangementStart::new(0, 0), None, &mut Cache::new(self));
}
}
impl Day12 {
pub fn create(input: String) -> Self {
return Day12 {
lines: input.lines().map(|l| GearLine::parse(l)).collect()
}
}
}
impl DaySolver for Day12 {
fn solve_part1(&mut self) -> String {
return self.lines.iter()
.map(|g| {
let arr = g.find_arrangements();
// println!("{} => {}", g, arr);
return arr;
})
.sum::<usize>().to_string();
}
fn solve_part2(&mut self) -> String {
return self.lines.iter()
.map(|g| {
let gears_str = g.gears.iter().join("");
let gears = (0..5).map(|_| gears_str.to_owned()).join("?");
// println!("{}", gears);
GearLine {
gears: gears.chars().collect_vec(),
sizes: g.sizes.repeat(5).into()
}
})
.map(|g| {
let res = g.find_arrangements();
// println!("{} => {}", g, res);
res
})
.sum::<usize>().to_string();
}
}
#[test]
fn test_part1() {
let mut day = Day12::create(read_file("input/day12_example.txt"));
assert_eq!("21", day.solve_part1());
}
#[test]
fn test_1_1() {
assert_eq!(1, GearLine::parse("???.### 1,1,3").find_arrangements());
}
#[test]
fn test_1_2() {
assert_eq!(4, GearLine::parse(".??..??...?##. 1,1,3").find_arrangements());
}
#[test]
fn test_part1_last() {
assert_eq!(10, GearLine::parse("?###???????? 3,2,1").find_arrangements());
}
#[test]
fn test_part2() {
let mut day = Day12::create(read_file("input/day12_example.txt"));
assert_eq!("525152", day.solve_part2());
}
#[test]
fn test_part2_2() {
assert_eq!(16384, GearLine::parse(".??..??...?##.?.??..??...?##.?.??..??...?##.?.??..??...?##.?.??..??...?##. 1,1,3,1,1,3,1,1,3,1,1,3,1,1,3").find_arrangements())
}

View File

@@ -3,6 +3,7 @@ extern crate core;
use std::time::Instant;
use crate::day10::Day10;
use crate::day11::Day11;
use crate::day12::Day12;
use crate::day1::Day1;
use crate::day2::Day2;
use crate::day3::Day3;
@@ -28,8 +29,8 @@ mod day8;
mod day9;
mod day10;
mod day11;
mod day12;
// const MAX_DAY: u8 = 11;
const DEFAULT_BENCHMARK_AMOUNT: u32 = 100;
fn main() {
@@ -111,6 +112,7 @@ fn build_day_solver(day: u8, input: String) -> Option<Box<dyn DaySolver>> {
9 => Some(Box::new(Day9::create(input))),
10 => Some(Box::new(Day10::create(input))),
11 => Some(Box::new(Day11::create(input))),
12 => Some(Box::new(Day12::create(input))),
_ => None
}
}