day6: complete solution + grid lib (used in solution)
This commit is contained in:
parent
49c37800a0
commit
145d779e83
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);
|
||||||
|
}
|
||||||
|
}
|
7
libs/grid/Cargo.lock
generated
Normal file
7
libs/grid/Cargo.lock
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "grid"
|
||||||
|
version = "0.1.0"
|
6
libs/grid/Cargo.toml
Normal file
6
libs/grid/Cargo.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "grid"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
188
libs/grid/src/lib.rs
Normal file
188
libs/grid/src/lib.rs
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
use std::{
|
||||||
|
fmt::{self, Debug, Display, Formatter, Write},
|
||||||
|
io::{BufRead, Cursor, Lines},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Grid<T> {
|
||||||
|
data: Vec<T>,
|
||||||
|
width: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Copy + Eq + PartialEq + Display + Debug> Grid<T> {
|
||||||
|
pub fn new(width: i64) -> Self {
|
||||||
|
Self {
|
||||||
|
data: Vec::new(),
|
||||||
|
width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn width(&self) -> usize {
|
||||||
|
return self.width as usize;
|
||||||
|
}
|
||||||
|
pub fn height(&self) -> usize {
|
||||||
|
return self.data.len() / self.width();
|
||||||
|
}
|
||||||
|
fn pos(&self, x: i64, y: i64) -> i64 {
|
||||||
|
y * self.width + x
|
||||||
|
}
|
||||||
|
fn coord(&self, pos: i64) -> Option<(i64, i64)> {
|
||||||
|
if pos < 0 || pos >= self.data.len() as i64 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some((pos % self.width, pos / self.width))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn valid_pos(&self, x: i64, y: i64) -> Option<usize> {
|
||||||
|
if x < 0 || x >= self.width {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if y < 0 || y >= self.data.len() as i64 / self.width {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let pos = self.pos(x, y);
|
||||||
|
if pos < 0 || pos as usize >= self.data.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.pos(x, y).try_into().ok()
|
||||||
|
}
|
||||||
|
pub fn get(&self, x: i64, y: i64) -> Option<T> {
|
||||||
|
match self.valid_pos(x, y) {
|
||||||
|
Some(pos) => Some(self.data[pos]),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn set(&mut self, x: i64, y: i64, val: T) -> bool {
|
||||||
|
match self.valid_pos(x, y) {
|
||||||
|
Some(pos) => {
|
||||||
|
self.data[pos] = val;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn row(&self, y: i64) -> Option<&[T]> {
|
||||||
|
if y < self.height() as i64 {
|
||||||
|
Some(&self.data[self.pos(0, y) as usize..self.pos(self.width, y) as usize])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find(&self, haystack: T) -> Option<(i64, i64)> {
|
||||||
|
self.coord(
|
||||||
|
self.data
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find_map(|(pos, val)| if *val == haystack { Some(pos as i64) } else { None })
|
||||||
|
.unwrap_or(-1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pub fn count(&self, haystack: T) -> usize {
|
||||||
|
self.data.iter().filter(|item| **item == haystack).count()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn forward_slice(&self, x: i64, y: i64, len: i64) -> Option<&[T]> {
|
||||||
|
let pos = (self.valid_pos(x, y), self.valid_pos(x + len - 1, y));
|
||||||
|
match pos {
|
||||||
|
(Some(pos1), Some(pos2)) => Some(&self.data[pos1..pos2 + 1]),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn window_compare_impl<const REV: bool>(&self, needle: &[T]) -> Vec<(i64, i64)> {
|
||||||
|
// if (self.width as usize) < needle.len() {
|
||||||
|
// return Vec::new();
|
||||||
|
// }
|
||||||
|
// let mut res = Vec::new();
|
||||||
|
// for y in 0..self.height() as i64 {
|
||||||
|
// let mut windows_tmp = self.row(y).unwrap().windows(needle.len());
|
||||||
|
// let windows = if REV {
|
||||||
|
// windows_tmp.rev()
|
||||||
|
// } else {
|
||||||
|
// windows_tmp
|
||||||
|
// };
|
||||||
|
|
||||||
|
// res.extend(
|
||||||
|
// windows
|
||||||
|
// .enumerate()
|
||||||
|
// .filter_map(|(x, w)| if w == needle { Some((x as i64, y)) } else { None }),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// res
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: BufRead> From<Lines<T>> for Grid<u8> {
|
||||||
|
fn from(input: Lines<T>) -> Grid<u8> {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
let mut width = 0;
|
||||||
|
for line in input.map(|i| i.unwrap()) {
|
||||||
|
if width == 0 {
|
||||||
|
width = line.len() as i64
|
||||||
|
} else if line.len() as i64 != width {
|
||||||
|
panic!("Grids must have fixed length rows")
|
||||||
|
}
|
||||||
|
data.extend_from_slice(line.as_bytes());
|
||||||
|
}
|
||||||
|
Grid { data, width }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Copy + Eq + PartialEq + Display + Debug> Display for Grid<T> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
for y in 0..self.height() {
|
||||||
|
for x in 0..self.width() {
|
||||||
|
self.get(x as i64, y as i64).fmt(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.write_char('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
static TEST_VECTOR: &str = &"ABCD
|
||||||
|
EFGH
|
||||||
|
IJKL
|
||||||
|
FBCG";
|
||||||
|
|
||||||
|
fn unchecked_load() -> Grid<u8> {
|
||||||
|
Grid::from(Cursor::new(TEST_VECTOR).lines())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_string() {
|
||||||
|
let grid = unchecked_load();
|
||||||
|
assert_eq!(grid.data, "ABCDEFGHIJKLFBCG".as_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn indexing() {
|
||||||
|
let grid = unchecked_load();
|
||||||
|
assert_eq!(grid.get(0, 0), Some(b'A'));
|
||||||
|
assert_eq!(grid.get(3, 3), Some(b'G'));
|
||||||
|
assert_eq!(grid.get(-1, 0), None);
|
||||||
|
assert_eq!(grid.get(0, -1), None);
|
||||||
|
assert_eq!(grid.get(5, 0), None);
|
||||||
|
assert_eq!(grid.get(0, 5), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn forward_slice() {
|
||||||
|
let grid = unchecked_load();
|
||||||
|
assert_eq!(grid.forward_slice(0, 0, 2), Some(b"AB".as_slice()));
|
||||||
|
assert_eq!(grid.forward_slice(2, 0, 2), Some(b"CD".as_slice()));
|
||||||
|
assert_eq!(grid.forward_slice(2, 0, 3), None);
|
||||||
|
assert_eq!(grid.forward_slice(0, 2, 4), Some(b"IJKL".as_slice()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn window_compare() {
|
||||||
|
let grid = unchecked_load();
|
||||||
|
assert_eq!(grid.window_compare(b"IJKL"), &[(0, 2)]);
|
||||||
|
assert_eq!(grid.window_compare(b"BC"), &[(1, 0), (1, 3)]);
|
||||||
|
assert_eq!(grid.window_compare(b"LF").len(), 0);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user