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:
202
21/src/main.rs
Normal file
202
21/src/main.rs
Normal 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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user