Skip to content

Commit

Permalink
submit day 10 part 2
Browse files Browse the repository at this point in the history
  • Loading branch information
dcastil committed Dec 16, 2023
1 parent 88ba4a6 commit cdf0e46
Show file tree
Hide file tree
Showing 2 changed files with 227 additions and 39 deletions.
10 changes: 10 additions & 0 deletions data/examples/10-2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FF7FSF7F7F7F7F7F---7
L|LJ||||||||||||F--J
FL-7LJLJ||||||LJL-77
F--JF--7||LJLJ7F7FJ-
L---JF-JLJ.||-FJLJJ7
|F|F-JF---7F7-L7L|7|
|FFJF7L7F-JF7|JL---7
7-L-JL7||F7|L7F-7F7|
L.L7LFJ|||||FJL7||LJ
L7JLJL-JLJLJL--JLJ.L
256 changes: 217 additions & 39 deletions src/bin/10.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,69 @@
use std::collections::HashSet;

advent_of_code::solution!(10);

pub fn part_one(input: &str) -> Option<u32> {
let map_grid = Grid::new(input);

let mut positions = map_grid.get_start_positions();
let mut positions = map_grid.start_positions();
let mut step_count = 0;

while step_count == 0 || !are_positions_equal(&positions) {
while {
for position in positions.iter_mut() {
*position = map_grid.get_next_position(position);
*position = map_grid.next_position(position);
}

step_count += 1;
}

!are_positions_equal(&positions)
} {}

Some(step_count)
}

pub fn part_two(input: &str) -> Option<u32> {
None
pub fn part_two(input: &str) -> Option<usize> {
let map_grid = Grid::new(input);
let mut visited_coordinates = HashSet::new();

let mut current_position = map_grid.start_positions().into_iter().next().unwrap();
let start_position = current_position.clone();
let mut right_turns: i32 = 0;

while {
let next_position = map_grid.next_position_with_start(&current_position, &start_position);

match current_position.direction.to_turn(&next_position.direction) {
Turn::Right => right_turns += 1,
Turn::Left => right_turns -= 1,
_ => (),
}

visited_coordinates.insert(next_position.coordinate.clone());

current_position = next_position;

start_position.coordinate != current_position.coordinate
} {}

let loop_tiles_count = visited_coordinates.len();
let is_clockwise = right_turns > 0;

while {
let next_position = map_grid.next_position_with_start(&current_position, &start_position);

map_grid.visit_inside_tiles(
&current_position,
&next_position,
is_clockwise,
&mut visited_coordinates,
);

current_position = next_position;

start_position.coordinate != current_position.coordinate
} {}

Some(visited_coordinates.len() - loop_tiles_count)
}

fn are_positions_equal(positions: &[Position]) -> bool {
Expand All @@ -41,8 +86,8 @@ impl Grid<'_> {
}
}

fn get_start_positions(&self) -> Vec<Position> {
let coordinate = self.get_start_coordinate();
fn start_positions(&self) -> Vec<Position> {
let coordinate = self.start_coordinate();

let mut start_positions = Vec::new();

Expand All @@ -69,7 +114,7 @@ impl Grid<'_> {
start_positions
}

fn get_start_coordinate(&self) -> Coordinate {
fn start_coordinate(&self) -> Coordinate {
for (y, line) in self.grid.iter().enumerate() {
for (x, &character) in line.as_bytes().iter().enumerate() {
if character == b'S' {
Expand All @@ -82,38 +127,105 @@ impl Grid<'_> {
}

fn is_connected(&self, position: &Position) -> bool {
if position.is_unsafe() {
if !self.is_in_bounds(position) {
return false;
}

let next_coordinate = position.get_next_coordinate_unchecked();
let next_coordinate = position.next_coordinate_unchecked();

self.get_char(next_coordinate).map_or(false, |&character| {
position.direction.is_connected(character)
})
position
.direction
.is_connected(*self.char_unchecked(&next_coordinate))
}

fn get_next_position(&self, position: &Position) -> Position {
let next_coordinate = position.get_next_coordinate_unchecked();
fn is_in_bounds(&self, position: &Position) -> bool {
(position.direction == Direction::Up && position.coordinate.y != 0)
|| (position.direction == Direction::Left && position.coordinate.x != 0)
|| (position.direction == Direction::Down
&& position.coordinate.y != self.grid.len() - 1)
|| (position.direction == Direction::Right
&& position.coordinate.x != self.grid[position.coordinate.y].len() - 1)
}

fn next_position(&self, position: &Position) -> Position {
let next_coordinate = position.next_coordinate_unchecked();
let next_direction = position
.direction
.get_next(*self.get_char_unchecked(&next_coordinate))
.next(*self.char_unchecked(&next_coordinate))
.unwrap();

Position::new(next_coordinate, next_direction)
}

fn get_char(&self, coordinate: Coordinate) -> Option<&u8> {
self.grid
.get(coordinate.y)
.and_then(|line| line.as_bytes().get(coordinate.x))
fn char_unchecked(&self, coordinate: &Coordinate) -> &u8 {
&self.grid[coordinate.y].as_bytes()[coordinate.x]
}

fn get_char_unchecked(&self, coordinate: &Coordinate) -> &u8 {
&self.grid[coordinate.y].as_bytes()[coordinate.x]
fn next_position_with_start(&self, position: &Position, start_position: &Position) -> Position {
let next_coordinate = position.next_coordinate_unchecked();
let next_character = *self.char_unchecked(&next_coordinate);

if next_character == b'S' {
start_position.clone()
} else {
let next_direction = position
.direction
.next(*self.char_unchecked(&next_coordinate))
.unwrap();

Position::new(next_coordinate, next_direction)
}
}

fn visit_inside_tiles(
&self,
current_position: &Position,
next_position: &Position,
is_clockwise: bool,
visited_coordinates: &mut HashSet<Coordinate>,
) {
let inside_directions = current_position
.direction
.to_inside_directions(&next_position.direction, is_clockwise);

for direction in inside_directions {
let position = next_position.with_direction(direction);

self.visit_tiles_recursively(&position, visited_coordinates);
}
}

fn visit_tiles_recursively(
&self,
position: &Position,
visited_coordinates: &mut HashSet<Coordinate>,
) {
if !self.is_in_bounds(position) {
return;
}

let coordinate = position.next_coordinate_unchecked();
let previous_direction = position.direction.inverse();

if visited_coordinates.insert(coordinate) {
for direction in [
Direction::Up,
Direction::Down,
Direction::Left,
Direction::Right,
] {
if direction != previous_direction {
self.visit_tiles_recursively(
&Position::new(position.next_coordinate_unchecked(), direction),
visited_coordinates,
)
}
}
}
}
}

#[derive(Clone, Debug)]
struct Position {
coordinate: Coordinate,
direction: Direction,
Expand All @@ -127,7 +239,14 @@ impl Position {
}
}

fn get_next_coordinate_unchecked(&self) -> Coordinate {
fn with_direction(&self, direction: Direction) -> Position {
Position {
coordinate: self.coordinate.clone(),
direction,
}
}

fn next_coordinate_unchecked(&self) -> Coordinate {
Coordinate::new(
match self.direction {
Direction::Left => self.coordinate.x - 1,
Expand All @@ -141,14 +260,9 @@ impl Position {
},
)
}

fn is_unsafe(&self) -> bool {
(self.coordinate.x == 0 && self.direction == Direction::Left)
|| (self.coordinate.y == 0 && self.direction == Direction::Up)
}
}

#[derive(PartialEq, Clone)]
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
struct Coordinate {
x: usize,
y: usize,
Expand All @@ -160,7 +274,7 @@ impl Coordinate {
}
}

#[derive(PartialEq, Debug)]
#[derive(PartialEq, Clone, Debug)]
enum Direction {
Up,
Down,
Expand All @@ -169,42 +283,106 @@ enum Direction {
}

impl Direction {
fn get_from_pipe_char(character: u8) -> [Direction; 2] {
fn from_pipe_char(character: u8) -> [Direction; 2] {
match character {
b'|' => [Direction::Up, Direction::Down],
b'-' => [Direction::Left, Direction::Right],
b'L' => [Direction::Up, Direction::Right],
b'J' => [Direction::Up, Direction::Left],
b'7' => [Direction::Down, Direction::Left],
b'F' => [Direction::Down, Direction::Right],
_ => panic!("Invalid pipe character"),
_ => panic!("Invalid pipe character {}", character as char),
}
}

fn from_turn_value(turn_value: u8) -> Direction {
match turn_value {
0 => Direction::Up,
1 => Direction::Right,
2 => Direction::Down,
3 => Direction::Left,
_ => panic!("Invalid turn value {}", turn_value),
}
}

fn is_connected(&self, character: u8) -> bool {
if character == b'.' {
false
} else {
Direction::get_from_pipe_char(character).contains(&self.get_inverse())
Direction::from_pipe_char(character).contains(&self.inverse())
}
}

fn get_next(&self, character: u8) -> Option<Direction> {
let inverse_direction = self.get_inverse();
fn next(&self, character: u8) -> Option<Direction> {
let inverse_direction = self.inverse();

Direction::get_from_pipe_char(character)
Direction::from_pipe_char(character)
.into_iter()
.find(|direction| *direction != inverse_direction)
}

fn get_inverse(&self) -> Direction {
fn inverse(&self) -> Direction {
match self {
Direction::Up => Direction::Down,
Direction::Down => Direction::Up,
Direction::Left => Direction::Right,
Direction::Right => Direction::Left,
}
}

fn to_turn(&self, next_direction: &Direction) -> Turn {
match (4 + self.turn_value() - next_direction.turn_value()) % 4 {
0 => Turn::None,
1 => Turn::Left,
3 => Turn::Right,
_ => panic!("Invalid turn from {:?} to {:?}", self, next_direction),
}
}

fn turn_value(&self) -> u8 {
match self {
Direction::Up => 0,
Direction::Right => 1,
Direction::Down => 2,
Direction::Left => 3,
}
}

fn to_inside_directions(
&self,
next_direction: &Direction,
is_clockwise: bool,
) -> Vec<Direction> {
let mut directions = Vec::new();

let mut current_turn_value = if is_clockwise {
next_direction.turn_value()
} else {
self.inverse().turn_value()
};

let end_turn_value = if is_clockwise {
self.inverse().turn_value()
} else {
next_direction.turn_value()
};

while {
current_turn_value = (current_turn_value + 1) % 4;

current_turn_value != end_turn_value
} {
directions.push(Direction::from_turn_value(current_turn_value));
}

directions
}
}

enum Turn {
Left,
Right,
None,
}

#[cfg(test)]
Expand All @@ -224,6 +402,6 @@ mod tests {
let result = part_two(&advent_of_code::template::read_file_part(
"examples", DAY, 2,
));
assert_eq!(result, None);
assert_eq!(result, Some(10));
}
}

0 comments on commit cdf0e46

Please sign in to comment.