Skip to content

Commit

Permalink
Day 18 solutions, similar to day 10
Browse files Browse the repository at this point in the history
  • Loading branch information
Cadiac committed Dec 18, 2023
1 parent 8b907dc commit e14468f
Show file tree
Hide file tree
Showing 7 changed files with 864 additions and 8 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,4 @@ This should start the server at `localhost:8080`.
❄️ [Day 15](aoc-solver/src/y2023/day15.rs)
❄️ [Day 16](aoc-solver/src/y2023/day16.rs)
❄️ [Day 17](aoc-solver/src/y2023/day17.rs)
❄️ [Day 18](aoc-solver/src/y2023/day18.rs)
6 changes: 1 addition & 5 deletions aoc-solver/src/y2023/day08.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,7 @@ impl Solution for Day08 {
match instruction {
'L' => current = &node.left,
'R' => current = &node.right,
_ => {
return Err(AocError::logic(format!(
"Unknown instruction {instruction}"
)))
}
_ => return Err(AocError::parse(instruction, "Unknown instruction")),
}

steps += 1;
Expand Down
4 changes: 2 additions & 2 deletions aoc-solver/src/y2023/day17.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ impl Direction {
}
}

fn delta(&self) -> (isize, isize) {
fn as_delta(&self) -> (isize, isize) {
match self {
Direction::North => (0, -1),
Direction::East => (1, 0),
Expand Down Expand Up @@ -120,7 +120,7 @@ fn dijkstra(grid: &Grid, min_consequtive: u8, max_consequtive: u8) -> Result<u32
}

for next_direction in direction.possible(consequtive, min_consequtive) {
let (dx, dy) = next_direction.delta();
let (dx, dy) = next_direction.as_delta();
let (x, y) = (position.0 + dx, position.1 + dy);

let next_consequtive = if direction == next_direction {
Expand Down
225 changes: 225 additions & 0 deletions aoc-solver/src/y2023/day18.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
use itertools::Itertools;

use crate::solution::{AocError, Solution};

pub struct Day18;

type Point = (f64, f64);

enum Direction {
Up,
Right,
Down,
Left,
}

impl Direction {
fn as_delta(&self) -> (isize, isize) {
match self {
Direction::Up => (0, -1),
Direction::Right => (1, 0),
Direction::Down => (0, 1),
Direction::Left => (-1, 0),
}
}
}

struct Instruction {
direction: Direction,
steps: isize,
color: String,
}

fn parse(input: &str) -> Result<Vec<Instruction>, AocError> {
let instructions = input
.trim()
.lines()
.map(|line| {
let (direction, steps, color) = line
.splitn(3, ' ')
.collect_tuple()
.ok_or(AocError::parse(line, "Invalid mapping"))?;

let direction = match direction {
"U" => Direction::Up,
"R" => Direction::Right,
"D" => Direction::Down,
"L" => Direction::Left,
_ => return Err(AocError::parse(direction, "Invalid direction")),
};

let steps = steps.parse().map_err(|err| AocError::parse(steps, err))?;
let color = color
.strip_prefix('(')
.and_then(|color| color.strip_suffix(')'))
.ok_or(AocError::parse(color, "invalid color"))?;

Ok(Instruction {
direction,
steps,
color: color.to_owned(),
})
})
.try_collect()?;

Ok(instructions)
}

fn execute(instructions: Vec<Instruction>) -> Result<(usize, Vec<Point>), AocError> {
let mut current = (0, 0);
let mut trench_len: isize = 0;
let mut vertices = Vec::new();

for instruction in instructions {
vertices.push((current.0 as f64, current.1 as f64));

let (dx, dy) = instruction.direction.as_delta();

trench_len += instruction.steps;

current.0 += instruction.steps * dx;
current.1 += instruction.steps * dy;
}

Ok((trench_len as usize, vertices))
}

fn shoelace(vertices: &[Point]) -> f64 {
let mut area = 0.0;
let n = vertices.len();

for i in 0..n {
let j = (i + 1) % n;

let x1 = vertices[i].0;
let y1 = vertices[i].1;
let x2 = vertices[j].0;
let y2 = vertices[j].1;

area += x1 * y2 - x2 * y1;
}

area.abs() / 2.0
}

impl Solution for Day18 {
type A = u64;
type B = u64;

fn default_input(&self) -> &'static str {
include_str!("../../../inputs/2023/day18.txt")
}

fn part_1(&self, input: &str) -> Result<u64, AocError> {
let instructions = parse(input)?;

let (trench_len, vertices) = execute(instructions)?;
let total_area = calculate_area(vertices, trench_len);

Ok(total_area)
}

fn part_2(&self, input: &str) -> Result<u64, AocError> {
let instructions = parse(input)?
.into_iter()
.map(|instruction| {
let color = instruction
.color
.strip_prefix('#')
.ok_or(AocError::parse(&instruction.color, "Missing #-prefix"))?;

let (steps, direction) = color.split_at(5);

let steps =
u64::from_str_radix(steps, 16).map_err(|err| AocError::parse(color, err))?;

let direction = match direction {
"0" => Direction::Right,
"1" => Direction::Down,
"2" => Direction::Left,
"3" => Direction::Up,
_ => return Err(AocError::parse(color, "Invalid direction mapping")),
};

Ok(Instruction {
direction,
steps: steps as isize,
color: instruction.color,
})
})
.try_collect()?;

let (trench_len, vertices) = execute(instructions)?;
let total_area = calculate_area(vertices, trench_len);

Ok(total_area)
}
}

fn calculate_area(vertices: Vec<(f64, f64)>, trench_len: usize) -> u64 {
// Calculate the area "A" of polygon using Shoelace formula
// https://en.wikipedia.org/wiki/Shoelace_formula
let area = shoelace(&vertices) as i64;

// Solve the amount of interior points "i" with Pick's theorem,
// using trench length as "b" and the area from shoelace as "A"
// https://en.wikipedia.org/wiki/Pick%27s_theorem
// A = i + b/2 - 1
// i = -b/2 + 1 + A
let interior_points = trench_len as i64 / -2 + 1 + area;

// Add together the volume dug out while digging the trench and
// the volume contained within it
trench_len as u64 + interior_points as u64
}

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

#[test]
fn it_solves_part1_example() {
assert_eq!(
Day18.part_1(
"R 6 (#70c710)\n\
D 5 (#0dc571)\n\
L 2 (#5713f0)\n\
D 2 (#d2c081)\n\
R 2 (#59c680)\n\
D 2 (#411b91)\n\
L 5 (#8ceee2)\n\
U 2 (#caa173)\n\
L 1 (#1b58a2)\n\
U 2 (#caa171)\n\
R 2 (#7807d2)\n\
U 3 (#a77fa3)\n\
L 2 (#015232)\n\
U 2 (#7a21e3)\n"
),
Ok(62)
);
}

#[test]
fn it_solves_part2_example() {
assert_eq!(
Day18.part_2(
"R 6 (#70c710)\n\
D 5 (#0dc571)\n\
L 2 (#5713f0)\n\
D 2 (#d2c081)\n\
R 2 (#59c680)\n\
D 2 (#411b91)\n\
L 5 (#8ceee2)\n\
U 2 (#caa173)\n\
L 1 (#1b58a2)\n\
U 2 (#caa171)\n\
R 2 (#7807d2)\n\
U 3 (#a77fa3)\n\
L 2 (#015232)\n\
U 2 (#7a21e3)\n"
),
Ok(952408144115)
);
}
}
5 changes: 4 additions & 1 deletion aoc-solver/src/y2023/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ pub mod day14;
pub mod day15;
pub mod day16;
pub mod day17;
pub mod day18;

pub const MAX_DAYS: u8 = 17;
pub const MAX_DAYS: u8 = 18;

pub struct Y2023;

Expand All @@ -42,6 +43,7 @@ impl Solver for Y2023 {
15 => day15::Day15.run(input, 15, 2023),
16 => day16::Day16.run(input, 16, 2023),
17 => day17::Day17.run(input, 17, 2023),
18 => day18::Day18.run(input, 18, 2023),
_ => vec![String::from("Solution not implemented (yet?)")],
}
}
Expand Down Expand Up @@ -76,6 +78,7 @@ impl Solver for Y2023 {
15 => include_str!("./day15.rs"),
16 => include_str!("./day16.rs"),
17 => include_str!("./day17.rs"),
18 => include_str!("./day18.rs"),
_ => unimplemented!(),
}
}
Expand Down
1 change: 1 addition & 0 deletions aoc-web/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub fn header(props: &HeaderProps) -> Html {
<NavLink route={Route::Solution { year: 2023, day: 15 }} current={props.route.clone()} text={"15"}/>
<NavLink route={Route::Solution { year: 2023, day: 16 }} current={props.route.clone()} text={"16"}/>
<NavLink route={Route::Solution { year: 2023, day: 17 }} current={props.route.clone()} text={"17"}/>
<NavLink route={Route::Solution { year: 2023, day: 18 }} current={props.route.clone()} text={"18"}/>
</>
}
},
Expand Down
Loading

0 comments on commit e14468f

Please sign in to comment.