From 80b3a907bcc1ad1d3f7ecede446c37940a4b0c67 Mon Sep 17 00:00:00 2001 From: Siphalor Date: Fri, 6 Dec 2024 22:45:12 +0100 Subject: [PATCH] Day six --- day-06-rust/.gitignore | 1 + day-06-rust/Cargo.lock | 7 ++ day-06-rust/Cargo.toml | 6 ++ day-06-rust/src/common.rs | 183 ++++++++++++++++++++++++++++++++++++++ day-06-rust/src/main.rs | 17 ++++ day-06-rust/src/task1.rs | 45 ++++++++++ day-06-rust/src/task2.rs | 144 ++++++++++++++++++++++++++++++ 7 files changed, 403 insertions(+) create mode 100644 day-06-rust/.gitignore create mode 100644 day-06-rust/Cargo.lock create mode 100644 day-06-rust/Cargo.toml create mode 100644 day-06-rust/src/common.rs create mode 100644 day-06-rust/src/main.rs create mode 100644 day-06-rust/src/task1.rs create mode 100644 day-06-rust/src/task2.rs diff --git a/day-06-rust/.gitignore b/day-06-rust/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/day-06-rust/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/day-06-rust/Cargo.lock b/day-06-rust/Cargo.lock new file mode 100644 index 0000000..575cdda --- /dev/null +++ b/day-06-rust/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "day-06-rust" +version = "0.1.0" diff --git a/day-06-rust/Cargo.toml b/day-06-rust/Cargo.toml new file mode 100644 index 0000000..8ad1e4b --- /dev/null +++ b/day-06-rust/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day-06-rust" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/day-06-rust/src/common.rs b/day-06-rust/src/common.rs new file mode 100644 index 0000000..1b1abb1 --- /dev/null +++ b/day-06-rust/src/common.rs @@ -0,0 +1,183 @@ +use std::collections::HashMap; + +pub type Coord = i16; + +#[derive(Debug, Copy, Clone)] +pub enum Direction { + North, + East, + South, + West, +} + +impl Direction { + pub fn next_clockwise(&self) -> Direction { + match self { + Direction::North => Direction::East, + Direction::East => Direction::South, + Direction::South => Direction::West, + Direction::West => Direction::North, + } + } + + pub fn next_counter_clockwise(&self) -> Direction { + match self { + Direction::North => Direction::West, + Direction::East => Direction::North, + Direction::South => Direction::East, + Direction::West => Direction::South, + } + } + + pub fn opposite(&self) -> Direction { + match self { + Direction::North => Direction::South, + Direction::East => Direction::West, + Direction::South => Direction::North, + Direction::West => Direction::East, + } + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +pub struct Position { + pub x: Coord, + pub y: Coord, +} + +impl Position { + pub fn offset(&self, direction: Direction) -> Position { + match direction { + Direction::North => Position { x: self.x, y: self.y - 1 }, + Direction::East => Position { x: self.x + 1, y: self.y }, + Direction::South => Position { x: self.x, y: self.y + 1 }, + Direction::West => Position { x: self.x - 1, y: self.y }, + } + } +} + +#[derive(Debug, Clone)] +pub struct Entity { + pub direction: Direction, + pub position: Position, +} + +#[derive(Debug, Clone)] +pub enum Tile { + Solid, + Empty(DirectionInfo), +} + +#[derive(Debug, Clone, Default)] +pub struct DirectionInfo { + pub north: bool, + pub east: bool, + pub south: bool, + pub west: bool, +} + +impl DirectionInfo { + pub fn mark(&mut self, direction: Direction) { + match direction { + Direction::North => self.north = true, + Direction::East => self.east = true, + Direction::South => self.south = true, + Direction::West => self.west = true, + } + } + + pub fn get(&self, direction: Direction) -> bool { + match direction { + Direction::North => self.north, + Direction::East => self.east, + Direction::South => self.south, + Direction::West => self.west, + } + } +} + +pub type Map = HashMap; + +pub struct Input { + pub map: Map, + pub guard: Option, +} + +pub fn parse_input(input: &str) -> Input { + let mut map = Map::new(); + let mut guard: Option = None; + + input.lines().enumerate().for_each(|(y, line)| { + line.chars().enumerate().for_each(|(x, ch)| { + let position = Position { + x: x as Coord, + y: y as Coord, + }; + map.insert( + position, + match ch { + '.' => Tile::Empty(Default::default()), + '^' => { + guard = Some(Entity { + position, + direction: Direction::North, + }); + Tile::Empty(DirectionInfo { + north: true, + ..Default::default() + }) + } + _ => Tile::Solid, + }, + ); + }) + }); + + + Input { map, guard } +} + +pub fn print_map(map: &Map) { + let max_pos = map.keys().fold(Position {x: 0, y: 0 }, |a, b| Position { x: a.x.max(b.x), y: a.y.max(b.y) }); + for y in 0..=max_pos.y { + let line = (0..=max_pos.x).map(|x| map.get(&Position{x, y})).collect::>(); + + for element in &line { + if let Some(Tile::Empty(DirectionInfo{north: true, ..})) = element { + print!(" ^ ") + } else { + print!(" ") + } + print!(" ") + } + print!("\n"); + for element in &line { + if let Some(Tile::Empty(DirectionInfo{west: true, ..})) = element { + print!("<") + } else { + print!(" ") + } + if let Some(Tile::Solid) = element { + print!("#") + } else { + print!(".") + } + if let Some(Tile::Empty(DirectionInfo{east: true, ..})) = element { + print!(">") + } else { + print!(" ") + } + print!(" ") + } + print!("\n"); + for element in &line { + if let Some(Tile::Empty(DirectionInfo{south: true, ..})) = element { + print!(" v ") + } else { + print!(" ") + } + print!(" ") + } + print!("\n"); + } +} \ No newline at end of file diff --git a/day-06-rust/src/main.rs b/day-06-rust/src/main.rs new file mode 100644 index 0000000..652b96c --- /dev/null +++ b/day-06-rust/src/main.rs @@ -0,0 +1,17 @@ +mod task1; +mod common; +mod task2; + +fn main() { + let args = std::env::args().collect::>(); + + if args.len() != 3 { + eprintln!("Usage: {} <1|2> ", args[0]); + } + + match args[1].as_str() { + "1" => task1::run(&*std::fs::read_to_string(&args[2]).expect("File not accessible")), + "2" => task2::run(&*std::fs::read_to_string(&args[2]).expect("File not accessible")), + _ => eprintln!("Unknown task: {}", args[1]), + } +} diff --git a/day-06-rust/src/task1.rs b/day-06-rust/src/task1.rs new file mode 100644 index 0000000..5c3ba2d --- /dev/null +++ b/day-06-rust/src/task1.rs @@ -0,0 +1,45 @@ +use crate::common::{parse_input, Entity, Input, Map, Tile}; + +pub fn run(input: &str) { + let Input { mut map, guard } = parse_input(input); + + let visited_count; + if let Some(mut guard) = guard { + wander_around(&mut map, &mut guard); + + visited_count = map + .values() + .into_iter() + .filter(|tile| { + if let Tile::Empty(visited) = tile { + visited.north || visited.east || visited.south || visited.west + } else { + false + } + }) + .count(); + } else { + visited_count = 0; + } + println!("Guard visited {} tiles", visited_count); +} + +fn wander_around(map: &mut Map, guard: &mut Entity) { + loop { + let next_position = guard.position.offset(guard.direction); + match map.get_mut(&next_position) { + Some(Tile::Empty(ref mut visited)) => { + guard.position = next_position; + if visited.get(guard.direction) { + // we're in a loop + break; + } + visited.mark(guard.direction); + } + None => break, + _ => { + guard.direction = guard.direction.next_clockwise(); + } + } + } +} diff --git a/day-06-rust/src/task2.rs b/day-06-rust/src/task2.rs new file mode 100644 index 0000000..5cc3e8d --- /dev/null +++ b/day-06-rust/src/task2.rs @@ -0,0 +1,144 @@ +use crate::common::{parse_input, print_map, Direction, Entity, Input, Map, Position, Tile}; +use std::collections::HashSet; + +pub fn run(input: &str) { + let Input { map, guard } = parse_input(input); + + if let Some(guard) = guard { + let clever_result = solve_clever(map.clone(), guard.clone()); + println!("Clever solution result: {:?}", clever_result.len()); + let brute_force_result = solve_brute_force(map.clone(), guard.clone()); + println!( + "Brute force solution result: {:?}", + brute_force_result.len() + ); + } +} + +fn solve_clever(mut map: Map, mut guard: Entity) -> HashSet { + let mut obstacle_positions = HashSet::::new(); + + cast_crumbs(&mut map, guard.position, guard.direction.opposite()); + loop { + let next_position = guard.position.offset(guard.direction); + let next_tile = map.get(&next_position); + + if let Some(Tile::Empty(crumbs)) = map.get(&guard.position) { + if crumbs.get(guard.direction.next_clockwise()) { + if let Some(Tile::Empty(_)) = next_tile { + obstacle_positions.insert(next_position); + } + } else if crumbs.get(guard.direction.opposite()) { + if let Some(Tile::Solid) = next_tile { + obstacle_positions.insert(guard.position.offset(guard.direction.next_clockwise())); + } else if let Some(Tile::Solid) = map.get(&guard.position.offset(guard.direction.next_clockwise())) { + obstacle_positions.insert(next_position); + } + } + } + + match next_tile { + None => break, + Some(Tile::Empty(_)) => { + if let Some(Tile::Empty(crumbs)) = map.get_mut(&guard.position) { + crumbs.mark(guard.direction); + } + + guard.position = next_position; + + if let Some(Tile::Solid) = + map.get(&next_position.offset(guard.direction.next_counter_clockwise())) + { + cast_crumbs(&mut map, guard.position, guard.direction); + } + } + Some(Tile::Solid) => { + guard.direction = guard.direction.next_clockwise(); + cast_crumbs(&mut map, guard.position, guard.direction.opposite()); + } + } + } + obstacle_positions +} + +fn cast_crumbs(map: &mut Map, mut position: Position, mut direction: Direction) { + loop { + let next_position = position.offset(direction); + match map.get_mut(&next_position) { + None => break, + Some(Tile::Empty(crumbs)) => { + if crumbs.get(direction.opposite()) { + return; + } else { + crumbs.mark(direction.opposite()) + } + position = next_position; + } + Some(Tile::Solid) => { + direction = direction.next_counter_clockwise(); + if let Some(Tile::Empty(crumbs)) = map.get_mut(&position) { + crumbs.mark(direction.opposite()) + } + } + } + } +} + +fn solve_brute_force(map: Map, mut guard: Entity) -> HashSet { + let mut obstacle_positions = HashSet::::new(); + let mut tested = HashSet::::new(); + + loop { + let next_position = guard.position.offset(guard.direction); + let next_tile = map.get(&next_position); + + if let Some(Tile::Empty(_)) = next_tile { + if !tested.contains(&next_position) { + tested.insert(next_position); + let mut world = map.clone(); + world.insert(next_position, Tile::Solid); + if check_for_loop(&mut world, guard.position, guard.direction) { + //print!(" =========================== {} ===========================", obstacle_positions.len()); + //print_map(&world); + obstacle_positions.insert(next_position); + } + } + } + + match next_tile { + None => break, + Some(Tile::Empty(_)) => { + guard.position = next_position; + } + Some(Tile::Solid) => { + guard.direction = guard.direction.next_clockwise(); + } + } + } + obstacle_positions +} + +fn check_for_loop(map: &mut Map, mut position: Position, mut direction: Direction) -> bool { + loop { + let next_position = position.offset(direction); + match map.get_mut(&next_position) { + None => return false, + Some(Tile::Empty(crumbs)) => { + if crumbs.get(direction) { + return true; + } + crumbs.mark(direction); + position = next_position; + } + Some(Tile::Solid) => { + direction = direction.next_clockwise(); + if let Some(Tile::Empty(crumbs)) = map.get_mut(&position) { + if crumbs.get(direction) { + return true; + } + crumbs.mark(direction); + } + } + } + } +}