Skip to content

Commit

Permalink
2024: day06
Browse files Browse the repository at this point in the history
  • Loading branch information
jstuczyn committed Dec 15, 2024
1 parent 595fc3a commit 3a2a0ba
Show file tree
Hide file tree
Showing 9 changed files with 412 additions and 2 deletions.
32 changes: 32 additions & 0 deletions 2024/day06/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "day06_2024"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
readme.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
name = "day06_2024"
path = "src/lib.rs"

[dependencies]
aoc-solution = { path = "../../aoc-solution" }
aoc-common = { path = "../../common" }
anyhow = { workspace = true }
winnow = { workspace = true }
rayon = { workspace = true }

[dev-dependencies]
criterion = { workspace = true }

[[bench]]
name = "benchmarks"
harness = false

[lints]
workspace = true
18 changes: 18 additions & 0 deletions 2024/day06/benches/benchmarks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2023 Jedrzej Stuczynski
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use aoc_common::define_aoc_benchmark;
use day06_2024::Day06;

define_aoc_benchmark!("inputs/2024/day06", Day06);
206 changes: 206 additions & 0 deletions 2024/day06/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
// Copyright 2024 Jedrzej Stuczynski
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::bail;
use aoc_common::types::Position;
use std::collections::HashSet;
use std::str::FromStr;
use winnow::ascii::line_ending;
use winnow::combinator::{alt, repeat, separated};
use winnow::token::literal;
use winnow::{PResult, Parser};

#[derive(Debug, Copy, Clone)]
enum MapFeature {
Empty,
Obstruction,
UpFacingGuard,
}

#[derive(Debug, Default, Copy, Clone, Hash, PartialEq, Eq)]
pub enum GuardDirection {
#[default]
Up,
Right,
Down,
Left,
}

#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct Guard {
position: Position,
direction: GuardDirection,
}

impl Guard {
fn rotate(&mut self) {
self.direction = match self.direction {
GuardDirection::Up => GuardDirection::Right,
GuardDirection::Right => GuardDirection::Down,
GuardDirection::Down => GuardDirection::Left,
GuardDirection::Left => GuardDirection::Up,
}
}

fn move_to(&mut self, new_position: Position) {
self.position = new_position;
}

fn in_front(&self) -> Position {
let (dx, dy) = match self.direction {
GuardDirection::Up => (0isize, -1isize),
GuardDirection::Right => (1, 0),
GuardDirection::Down => (0, 1),
GuardDirection::Left => (-1, 0),
};
self.position + (dx, dy)
}
}

fn map_feature_parser(input: &mut &str) -> PResult<MapFeature> {
alt((
literal('.').value(MapFeature::Empty),
literal('#').value(MapFeature::Obstruction),
literal('^').value(MapFeature::UpFacingGuard),
))
.parse_next(input)
}

fn row_parser(input: &mut &str) -> PResult<Vec<MapFeature>> {
repeat(1.., map_feature_parser).parse_next(input)
}

#[derive(Debug, Clone)]
struct RawMap {
rows: Vec<Vec<MapFeature>>,
}

impl FromStr for RawMap {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let rows = separated(1.., row_parser, line_ending)
.parse(s.trim())
.map_err(|err| anyhow::format_err!("{err}"))?;

Ok(RawMap { rows })
}
}

impl TryFrom<RawMap> for Map {
type Error = anyhow::Error;

fn try_from(value: RawMap) -> Result<Self, Self::Error> {
let mut obstacles = HashSet::new();
let mut guard = Guard::default();

let height = value.rows.len();
let width = value.rows[0].len();
for (y, row) in value.rows.into_iter().enumerate() {
for (x, feature) in row.into_iter().enumerate() {
let pos = Position::from((x, y));
match feature {
MapFeature::Empty => {}
MapFeature::Obstruction => {
obstacles.insert(pos);
}
MapFeature::UpFacingGuard => {
guard.position = pos;
}
}
}
}

if guard.position.is_origin() {
bail!("failed to find initial guard position")
}

Ok(Map {
guard,
obstacles,
dimensions: Rect { width, height },
})
}
}

impl FromStr for Map {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
RawMap::from_str(s)?.try_into()
}
}

#[derive(Clone, Copy, Debug)]
pub struct Rect {
pub width: usize,
pub height: usize,
}

#[derive(Clone, Debug)]
pub struct Map {
guard: Guard,
obstacles: HashSet<Position>,
pub dimensions: Rect,
}

impl Map {
pub fn is_outside_map(&self, pos: Position) -> bool {
pos.x < 0
|| pos.y < 0
|| pos.x >= self.dimensions.width as isize
|| pos.y >= self.dimensions.height as isize
}

pub fn has_obstacle(&self, position: Position) -> bool {
self.obstacles.contains(&position)
}

pub fn move_guard(&mut self) -> Position {
loop {
let front = self.guard.in_front();
if self.has_obstacle(front) {
self.guard.rotate()
} else {
self.guard.move_to(front);
return front;
}
}
}

pub fn guard_position(&self) -> Position {
self.guard.position
}

pub fn test_loop(mut self) -> bool {
let mut visited = HashSet::new();
visited.insert(self.guard);

loop {
let next_guard_position = self.move_guard();
if !visited.insert(self.guard) {
return true;
}
if self.is_outside_map(next_guard_position) {
return false;
}
}
}

pub fn new_with_obstacle(&self, obstacle: Position) -> Self {
let mut tester = self.clone();
tester.obstacles.insert(obstacle);
tester
}
}
112 changes: 112 additions & 0 deletions 2024/day06/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2024 Jedrzej Stuczynski
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::common::Map;
use aoc_common::parsing::FromStrParser;
use aoc_solution::Aoc;
use rayon::prelude::*;
use std::collections::HashSet;

mod common;

#[derive(Aoc)]
#[aoc(input = Map)]
#[aoc(parser = FromStrParser)]
#[aoc(part1(output = usize, runner = part1))]
#[aoc(part2(output = usize, runner = part2))]
pub struct Day06;

pub fn part1(mut input: Map) -> usize {
let mut visited = HashSet::new();
visited.insert(input.guard_position());
loop {
let next_guard_position = input.move_guard();

if input.is_outside_map(next_guard_position) {
return visited.len();
}

visited.insert(next_guard_position);
}
}

pub fn part2(mut input: Map) -> usize {
let base_loop_tester = input.clone();

// 1. get all possible spots visited by the guard
let mut visited = HashSet::new();
loop {
let next_guard_position = input.move_guard();

if input.is_outside_map(next_guard_position) {
break;
}
visited.insert(next_guard_position);
}

// 2. for each of them, try to put an obstacle on the way to see if it would result in a loop
// single-threaded:
// let mut loops = 0;
// for possible_obstacle in visited {
// if base_loop_tester
// .new_with_obstacle(possible_obstacle)
// .test_loop()
// {
// loops += 1
// }
// }

visited
.par_iter()
.map(|pos| {
if base_loop_tester.new_with_obstacle(*pos).test_loop() {
1
} else {
0
}
})
.sum()
}

#[cfg(test)]
mod tests {
use super::*;

fn sample_input() -> Map {
r#"....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#..."#
.parse()
.unwrap()
}

#[test]
fn part1_sample_input() {
let expected = 41;
assert_eq!(expected, part1(sample_input()))
}

#[test]
fn part2_sample_input() {
let expected = 6;
assert_eq!(expected, part2(sample_input()))
}
}
22 changes: 22 additions & 0 deletions 2024/day06/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2024 Jedrzej Stuczynski
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use aoc_common::helpers::root_path;
use aoc_solution::AocSolutionSolver;
use day06_2024::Day06;

#[cfg(not(tarpaulin_include))]
fn main() {
Day06::try_solve_from_file(root_path("inputs/2024/day06"))
}
Loading

0 comments on commit 3a2a0ba

Please sign in to comment.