[TASK] Finally(!) completed day 12
This commit is contained in:
1000
input/day12.txt
Normal file
1000
input/day12.txt
Normal file
File diff suppressed because it is too large
Load Diff
6
input/day12_example.txt
Normal file
6
input/day12_example.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
???.### 1,1,3
|
||||
.??..??...?##. 1,1,3
|
||||
?#?#?#?#?#?#?#? 1,3,1,6
|
||||
????.#...#... 4,1,1
|
||||
????.######..#####. 1,6,5
|
||||
?###???????? 3,2,1
|
||||
@@ -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
274
src/day12.rs
Normal 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())
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user