day21: problem 2 solution

not proud of this one either, completely cheated and it does not get
exactly the correct error (it was off by one for my input), but the
quadratic solver on Wolfram Alpha was able to do it.
This commit is contained in:
2023-12-21 03:17:11 -08:00
parent eb6c1f42cd
commit 190fc92842
3 changed files with 321 additions and 92 deletions

View File

@ -1,9 +1,7 @@
use core::panic;
use std::collections::HashSet;
use indicatif::{ProgressBar, ProgressStyle};
use petgraph::algo::k_shortest_path;
use petgraph::prelude::*;
use rayon::prelude::*;
use std::collections::HashSet;
use std::fs::File;
use std::io::{BufRead, BufReader, Lines};
use std::time::Instant;
@ -31,46 +29,33 @@ fn main() {
// PARSE
type Position = (usize, usize);
type Position = (isize, isize);
type WrappedPosition = (usize, usize);
type Offset = (isize, isize);
#[derive(Debug, Clone)]
struct MapTile {
c: char,
idx: NodeIndex,
}
impl MapTile {
fn new(c: char, idx: NodeIndex) -> Self {
Self { c, idx }
fn new(c: char) -> Self {
Self { c }
}
}
type GraphType = DiGraph<char, ()>;
struct GardenMap {
map: Vec<Vec<MapTile>>,
graph: GraphType,
start: Position,
}
impl<T: BufRead> From<Lines<T>> for GardenMap {
fn from(lines: Lines<T>) -> Self {
let mut graph = DiGraph::new();
let map = lines
.map(|line| {
line.unwrap()
.chars()
.map(|c| MapTile::new(c, graph.add_node(c)))
.collect()
})
.map(|line| line.unwrap().chars().map(|c| MapTile::new(c)).collect())
.collect();
let mut new = Self {
map,
start: (0, 0),
graph,
};
let mut new = Self { map, start: (0, 0) };
new.find_start();
new.build_graph();
new
}
}
@ -78,6 +63,21 @@ impl<T: BufRead> From<Lines<T>> for GardenMap {
const ADJACENCIES: [Offset; 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)];
impl GardenMap {
fn wrap_pos(&self, pos: &Position) -> WrappedPosition {
let (width, height) = (self.width() as isize, self.height() as isize);
(
if pos.0 < 0 {
(pos.0 + (-pos.0 / width + 1) * width) as usize % self.width()
} else {
pos.0 as usize % self.width()
},
if pos.1 < 0 {
(pos.1 + (-pos.1 / height + 1) * height) as usize % self.height()
} else {
pos.1 as usize % self.height()
},
)
}
fn width(&self) -> usize {
self.map[0].len()
}
@ -85,80 +85,103 @@ impl GardenMap {
self.map.len()
}
fn at(&self, pos: &Position) -> &MapTile {
let pos = self.wrap_pos(pos);
&self.map[pos.1][pos.0]
}
fn at_mut(&mut self, pos: &Position) -> &mut MapTile {
&mut self.map[pos.1][pos.0]
}
// return the valid 'moves' from pos
fn adjacent_to(&self, pos: &Position) -> Vec<Position> {
ADJACENCIES
.iter()
.filter_map(|ofs| self.offset_pos(pos, ofs))
.filter(|pos| self.at(pos).c == '.' || self.at(pos).c == 'S')
.filter(|pos| self.at(pos).c != '#')
.collect()
}
fn offset_pos(&self, pos: &Position, ofs: &Offset) -> Option<Position> {
let new_pos = (pos.0 as isize + ofs.0, pos.1 as isize + ofs.1);
if new_pos.0 < 0 || new_pos.1 < 0 || new_pos.0 >= self.width() as isize || new_pos.1 >= self.height() as isize {
return None;
}
return Some((new_pos.0 as usize, new_pos.1 as usize));
return Some((new_pos.0, new_pos.1));
}
fn find_start(&mut self) {
for (y, row) in self.map.iter().enumerate() {
for (x, tile) in row.iter().enumerate() {
if tile.c == 'S' {
self.start = (x, y);
self.start = (x as isize, y as isize);
return;
}
}
}
panic!("didn't find the start square!");
}
fn build_graph(&mut self) {
for y in 0..self.height() {
for x in 0..self.width() {
for (x2, y2) in self.adjacent_to(&(x, y)) {
self.graph.add_edge(self.at(&(x, y)).idx, self.at(&(x2, y2)).idx, ());
}
}
}
}
fn reachable_after(&self, from: &Position, n: usize) -> HashSet<Position> {
fn reachable_after(&self, from: &Position, n: usize) -> u64 {
let bar = ProgressBar::new(n as u64).with_style(
ProgressStyle::with_template(
"[{elapsed_precise}/{eta_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {per_sec}",
)
.unwrap(),
);
let mut visited_after: Vec<HashSet<Position>> = Vec::new();
visited_after.push(HashSet::from([*from]));
for i in 1..n + 1 {
for i in 1..n+1 {
visited_after.push(
visited_after[i - 1]
.iter()
.flat_map(|last| self.adjacent_to(last))
.collect(),
);
bar.inc(1);
// if primes::is_prime(i as u64) {
// println!("count after {} steps: {}", i, visited_after[i].len());
// }
}
visited_after[n].clone()
visited_after[n].len() as u64
}
fn reachable_count_after(&self, from: &Position, n: usize) -> u64 {
self.reachable_after(from, n).len() as u64
let dim = self.width() as f64;
let target_mod = (n % self.width()) as f64;
let x_values:Vec<f64> = vec![target_mod, target_mod + dim, target_mod + 2.*dim];
let y_values:Vec<f64> = x_values.iter().map(|n| self.reachable_after(from, *n as usize) as f64).collect();
let coeffs = polyfit_rs::polyfit_rs::polyfit(
&x_values,
&y_values,
2,
).unwrap();
println!("values: x: {:?} y: {:?}", x_values, y_values);
println!("coefficients: {:?}", coeffs);
let f_n= n as f64;
let result = coeffs[0] + coeffs[1] * f_n + coeffs[2] * f_n.powf(2.0);
result.round() as u64
}
fn draw_with_bounds(&self, from: &Position, to: &Position) {
for row in from.1..to.1 + 1 {
for col in from.0..to.0 + 1 {
print!("{}", self.at(&(col, row)).c);
}
println!();
}
}
}
fn print_visited(map: &GardenMap, visited: &Vec<Vec<bool>>) {
for (y, row) in visited.iter().enumerate() {
for (x, cell) in row.iter().enumerate() {
print!("{}", if *cell { 'O' } else { map.at(&(x, y)).c });
}
println!();
}
}
// fn print_visited(map: &GardenMap, visited: &Vec<Vec<bool>>) {
// for (y, row) in visited.iter().enumerate() {
// for (x, cell) in row.iter().enumerate() {
// print!("{}", if *cell { 'O' } else { map.at(&(x, y)).c });
// }
// println!();
// }
// }
// PROBLEM 1 solution
fn problem1_impl<T: BufRead>(input: Lines<T>, n: usize) -> u64 {
let map = GardenMap::from(input);
// println!("map: {:?} start: {:?}", map.map, &map.start);
// map.draw_with_bounds(
// &(-(map.width() as isize), -(map.height() as isize)),
// &(map.width() as isize * 2 + 1, map.height() as isize * 2 + 1),
// );
map.reachable_count_after(&map.start, n)
}
@ -168,13 +191,14 @@ fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
// PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
0
problem1_impl(input, 26501365)
}
#[cfg(test)]
mod tests {
use crate::*;
use std::io::Cursor;
use test_case::test_case;
const EXAMPLE: &str = &"...........
.....###.#.
@ -194,9 +218,13 @@ mod tests {
assert_eq!(problem1_impl(c.lines(), 6), 16);
}
#[test]
fn problem2_example() {
#[test_case(6, 16)]
#[test_case(10, 50)]
#[test_case(50, 1594)]
#[test_case(100, 6536)]
#[test_case(500, 167004)]
fn problem2_example(n: usize, expect: u64) {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem2(c.lines()), 0);
assert_eq!(problem1_impl(c.lines(), n), expect);
}
}