day 21: problem 1 solution

another unsatisfying one where i needed a visual hint to grok what i
should be doing.

a bunch of graph building stuff that wasn't needed for the part 1
solution at all.
This commit is contained in:
2023-12-21 00:33:28 -08:00
parent 512b05f624
commit eb6c1f42cd
4 changed files with 610 additions and 0 deletions

202
21/src/main.rs Normal file
View File

@ -0,0 +1,202 @@
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::fs::File;
use std::io::{BufRead, BufReader, Lines};
use std::time::Instant;
// BOILERPLATE
type InputIter = Lines<BufReader<File>>;
fn get_input() -> InputIter {
let f = File::open("input").unwrap();
let br = BufReader::new(f);
br.lines()
}
fn main() {
let start = Instant::now();
let ans1 = problem1(get_input());
let duration = start.elapsed();
println!("Problem 1 solution: {} [{}s]", ans1, duration.as_secs_f64());
let start = Instant::now();
let ans2 = problem2(get_input());
let duration = start.elapsed();
println!("Problem 2 solution: {} [{}s]", ans2, duration.as_secs_f64());
}
// PARSE
type Position = (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 }
}
}
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()
})
.collect();
let mut new = Self {
map,
start: (0, 0),
graph,
};
new.find_start();
new.build_graph();
new
}
}
const ADJACENCIES: [Offset; 4] = [(-1, 0), (1, 0), (0, -1), (0, 1)];
impl GardenMap {
fn width(&self) -> usize {
self.map[0].len()
}
fn height(&self) -> usize {
self.map.len()
}
fn at(&self, pos: &Position) -> &MapTile {
&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')
.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));
}
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);
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> {
let mut visited_after: Vec<HashSet<Position>> = Vec::new();
visited_after.push(HashSet::from([*from]));
for i in 1..n + 1 {
visited_after.push(
visited_after[i - 1]
.iter()
.flat_map(|last| self.adjacent_to(last))
.collect(),
);
}
visited_after[n].clone()
}
fn reachable_count_after(&self, from: &Position, n: usize) -> u64 {
self.reachable_after(from, n).len() as u64
}
}
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.reachable_count_after(&map.start, n)
}
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
problem1_impl(input, 64)
}
// PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
0
}
#[cfg(test)]
mod tests {
use crate::*;
use std::io::Cursor;
const EXAMPLE: &str = &"...........
.....###.#.
.###.##..#.
..#.#...#..
....#.#....
.##..S####.
.##..#...#.
.......##..
.##.#.####.
.##..##.##.
...........";
#[test]
fn problem1_example() {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem1_impl(c.lines(), 6), 16);
}
#[test]
fn problem2_example() {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem2(c.lines()), 0);
}
}