day6: complete solution + grid lib (used in solution)
This commit is contained in:
14
6/Cargo.lock
generated
Normal file
14
6/Cargo.lock
generated
Normal file
@ -0,0 +1,14 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "day6"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"grid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "grid"
|
||||
version = "0.1.0"
|
7
6/Cargo.toml
Normal file
7
6/Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "day6"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
grid = { version = "0.1.0", path = "../libs/grid" }
|
227
6/src/main.rs
Normal file
227
6/src/main.rs
Normal file
@ -0,0 +1,227 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Display;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Lines};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
// BOILERPLATE
|
||||
type InputIter = Lines<BufReader<File>>;
|
||||
|
||||
pub fn get_input() -> InputIter {
|
||||
let f = File::open("input").unwrap();
|
||||
let br = BufReader::new(f);
|
||||
br.lines()
|
||||
}
|
||||
|
||||
fn duration_format(duration: Duration) -> String {
|
||||
match duration.as_secs_f64() {
|
||||
x if x > 1.0 => format!("{:.3}s", x),
|
||||
x if x > 0.010 => format!("{:.3}ms", x * 1e3),
|
||||
x => format!("{:.3}us", x * 1e6),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let input = get_input();
|
||||
let start = Instant::now();
|
||||
let ans1 = problem1(input);
|
||||
let duration1 = start.elapsed();
|
||||
println!("Problem 1 solution: {} [{}]", ans1, duration_format(duration1));
|
||||
|
||||
let input = get_input();
|
||||
let start = Instant::now();
|
||||
let ans2 = problem2(input);
|
||||
let duration2 = start.elapsed();
|
||||
println!("Problem 2 solution: {} [{}]", ans2, duration_format(duration2));
|
||||
println!("Total duration: {}", duration_format(duration1 + duration2));
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
enum FacingDirection {
|
||||
Up = b'^',
|
||||
Down = b'v',
|
||||
Left = b'<',
|
||||
Right = b'>',
|
||||
}
|
||||
|
||||
impl FacingDirection {
|
||||
fn next(&self) -> FacingDirection {
|
||||
match self {
|
||||
FacingDirection::Up => FacingDirection::Right,
|
||||
FacingDirection::Down => FacingDirection::Left,
|
||||
FacingDirection::Left => FacingDirection::Up,
|
||||
FacingDirection::Right => FacingDirection::Down,
|
||||
}
|
||||
}
|
||||
fn pos_ofs(&self, pos: (i64, i64)) -> (i64, i64) {
|
||||
match self {
|
||||
FacingDirection::Up => (pos.0 + 0, pos.1 + -1),
|
||||
FacingDirection::Down => (pos.0 + 0, pos.1 + 1),
|
||||
FacingDirection::Left => (pos.0 + -1, pos.1 + 0),
|
||||
FacingDirection::Right => (pos.0 + 1, pos.1 + 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum StepOutcome {
|
||||
LeftMap,
|
||||
LoopFound,
|
||||
Continue,
|
||||
}
|
||||
|
||||
enum RunOutcome {
|
||||
LeftMap,
|
||||
LoopFound,
|
||||
Stuck,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Map {
|
||||
grid: grid::Grid<u8>,
|
||||
visited_from: HashMap<(i64, i64), HashSet<FacingDirection>>,
|
||||
guard_facing: FacingDirection,
|
||||
guard_pos: (i64, i64),
|
||||
}
|
||||
|
||||
impl<T: BufRead> From<Lines<T>> for Map {
|
||||
fn from(input: Lines<T>) -> Self {
|
||||
let grid = grid::Grid::from(input);
|
||||
let visited_from = HashMap::new();
|
||||
let guard_pos = grid.find(b'^').expect("Guard not found");
|
||||
let guard_facing = FacingDirection::Up;
|
||||
Self {
|
||||
grid,
|
||||
guard_pos,
|
||||
guard_facing,
|
||||
visited_from,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Map {
|
||||
fn look(&self, dir: &FacingDirection) -> Option<u8> {
|
||||
match dir {
|
||||
FacingDirection::Up => self.grid.get(self.guard_pos.0, self.guard_pos.1 - 1),
|
||||
FacingDirection::Down => self.grid.get(self.guard_pos.0, self.guard_pos.1 + 1),
|
||||
FacingDirection::Left => self.grid.get(self.guard_pos.0 - 1, self.guard_pos.1),
|
||||
FacingDirection::Right => self.grid.get(self.guard_pos.0 + 1, self.guard_pos.1),
|
||||
}
|
||||
}
|
||||
/// Move one step in the facing direction, return if we are still inside the bounds
|
||||
fn step_guard(&mut self) -> StepOutcome {
|
||||
let new_pos = self.guard_facing.pos_ofs(self.guard_pos);
|
||||
if self.visited_from.contains_key(&new_pos) && self.visited_from[&new_pos].contains(&self.guard_facing) {
|
||||
return StepOutcome::LoopFound;
|
||||
}
|
||||
if self.grid.set(new_pos.0, new_pos.1, b'X') {
|
||||
self.visited_from
|
||||
.entry(new_pos)
|
||||
.or_insert(HashSet::new())
|
||||
.insert(self.guard_facing);
|
||||
self.guard_pos = new_pos;
|
||||
StepOutcome::Continue
|
||||
} else {
|
||||
StepOutcome::LeftMap
|
||||
}
|
||||
}
|
||||
fn run_guard(&mut self) -> RunOutcome {
|
||||
// if the guard is surrounded by obstacles, bail out
|
||||
if self
|
||||
.grid
|
||||
.get(self.guard_pos.0 - 1, self.guard_pos.1)
|
||||
.is_some_and(|v| v == b'#')
|
||||
&& self
|
||||
.grid
|
||||
.get(self.guard_pos.0 + 1, self.guard_pos.1)
|
||||
.is_some_and(|v| v == b'#')
|
||||
&& self
|
||||
.grid
|
||||
.get(self.guard_pos.0, self.guard_pos.1 - 1)
|
||||
.is_some_and(|v| v == b'#')
|
||||
&& self
|
||||
.grid
|
||||
.get(self.guard_pos.0, self.guard_pos.1 + 1)
|
||||
.is_some_and(|v| v == b'#')
|
||||
{
|
||||
return RunOutcome::Stuck;
|
||||
}
|
||||
while let Some(val) = self.look(&self.guard_facing) {
|
||||
match val {
|
||||
b'#' => {
|
||||
// obstacle, turn right
|
||||
self.guard_facing = self.guard_facing.next();
|
||||
}
|
||||
_ => match self.step_guard() {
|
||||
StepOutcome::LeftMap => return RunOutcome::LeftMap,
|
||||
StepOutcome::LoopFound => return RunOutcome::LoopFound,
|
||||
StepOutcome::Continue => {}
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
return RunOutcome::LeftMap;
|
||||
}
|
||||
}
|
||||
|
||||
// PROBLEM 1 solution
|
||||
|
||||
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
|
||||
let mut map = Map::from(input);
|
||||
eprintln!("problem 1");
|
||||
map.run_guard();
|
||||
|
||||
(map.grid.count(b'X') + map.grid.count(b'-') + map.grid.count(b'|') + map.grid.count(b'^')) as u64
|
||||
}
|
||||
|
||||
// PROBLEM 2 solution
|
||||
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
|
||||
let input_map = Map::from(input);
|
||||
let mut loop_count = 0u64;
|
||||
for y in 0..input_map.grid.height() {
|
||||
for x in 0..input_map.grid.width() {
|
||||
eprintln!("Replacing ({}, {}) with obstacle", x, y);
|
||||
match input_map.grid.get(x as i64, y as i64) {
|
||||
Some(b'.') => {
|
||||
let mut test_map = input_map.clone();
|
||||
test_map.grid.set(x as i64, y as i64, b'#');
|
||||
match test_map.run_guard() {
|
||||
RunOutcome::LoopFound => loop_count += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
loop_count
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::*;
|
||||
use std::io::Cursor;
|
||||
|
||||
const EXAMPLE: &str = &"....#.....
|
||||
.........#
|
||||
..........
|
||||
..#.......
|
||||
.......#..
|
||||
..........
|
||||
.#..^.....
|
||||
........#.
|
||||
#.........
|
||||
......#...";
|
||||
|
||||
#[test]
|
||||
fn problem1_example() {
|
||||
let c = Cursor::new(EXAMPLE);
|
||||
assert_eq!(problem1(c.lines()), 41);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn problem2_example() {
|
||||
let c = Cursor::new(EXAMPLE);
|
||||
assert_eq!(problem2(c.lines()), 6);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user