From 0000034077e6846612dc289c4e667eb93a20c60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Jane=C5=BEi=C4=8D?= Date: Mon, 18 Dec 2023 21:02:50 +0100 Subject: [PATCH] solution: day 18 --- data/examples/18.txt | 14 +++++ src/bin/18.rs | 126 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 data/examples/18.txt create mode 100644 src/bin/18.rs diff --git a/data/examples/18.txt b/data/examples/18.txt new file mode 100644 index 000000000..fc7612e --- /dev/null +++ b/data/examples/18.txt @@ -0,0 +1,14 @@ +R 6 (#70c710) +D 5 (#0dc571) +L 2 (#5713f0) +D 2 (#d2c081) +R 2 (#59c680) +D 2 (#411b91) +L 5 (#8ceee2) +U 2 (#caa173) +L 1 (#1b58a2) +U 2 (#caa171) +R 2 (#7807d2) +U 3 (#a77fa3) +L 2 (#015232) +U 2 (#7a21e3) diff --git a/src/bin/18.rs b/src/bin/18.rs new file mode 100644 index 000000000..2bf1150 --- /dev/null +++ b/src/bin/18.rs @@ -0,0 +1,126 @@ +use std::str::FromStr; + +#[derive(Debug, Clone, Copy)] +enum Direction { + Up, + Down, + Left, + Right, +} + +#[derive(Debug)] +struct ParseDirectionError; + +impl FromStr for Direction { + type Err = ParseDirectionError; + fn from_str(s: &str) -> Result { + use Direction::*; + + Ok(match s { + "U" | "3" => Up, + "D" | "1" => Down, + "L" | "2" => Left, + "R" | "0" => Right, + _ => return Err(ParseDirectionError), + }) + } +} + +impl From for (isize, isize) { + fn from(value: Direction) -> Self { + use Direction::*; + match value { + Up => (-1, 0), + Down => (1, 0), + Left => (0, -1), + Right => (0, 1), + } + } +} + +fn get_area(border: &[(isize, isize)], border_length: isize) -> isize { + // get area with shoelace formula (trapezoid variant) + // https://en.wikipedia.org/wiki/Shoelace_formula + let mut shoelace: isize = 0; + for n in 0..border.len() { + let (y1, x1) = border[n]; + let (y2, x2) = border[(n + 1) % border.len()]; + shoelace += (y1 + y2) * (x1 - x2); + } + let area = shoelace / 2; + + // get interior by inverting pick's theorem formula + // https://en.wikipedia.org/wiki/Pick%27s_theorem + let interior = area + 1 - border_length / 2; + interior + border_length +} + +fn get_border(instructions: &[(Direction, isize)]) -> (Vec<(isize, isize)>, isize) { + let mut border = Vec::new(); + let mut border_length = 0; + let (mut sy, mut sx) = (0, 0); + border.push((sy, sx)); + + for (d, l) in instructions.iter().copied() { + let (dy, dx) = d.into(); + (sy, sx) = (sy + l * dy, sx + l * dx); + border.push((sy, sx)); + border_length += l; + } + + border.pop(); + + (border, border_length) +} + +pub fn part_one(input: &str) -> Option { + let instructions: Vec<(Direction, isize)> = input + .lines() + .map(|line| { + let [d, l, _] = line.splitn(3, ' ').collect::>().try_into().unwrap(); + (d.parse().unwrap(), l.parse::().unwrap()) + }) + .collect(); + + let (border, border_length) = get_border(&instructions); + Some(get_area(&border, border_length)) +} + +fn join_option_tuple((a, b): (Option, Option)) -> Option<(T, U)> { + Some((a?, b?)) +} + +pub fn part_two(input: &str) -> Option { + let instructions: Vec<(Direction, isize)> = input + .lines() + .filter_map(|line| line.split_once(" (#")) + .filter_map(|(_, h)| h.strip_suffix(')')) + .map(|h| h.split_at(h.len() - 1)) + .map(|(hex, dir)| (dir.parse().ok(), isize::from_str_radix(hex, 16).ok())) + .filter_map(join_option_tuple) + .collect(); + + let (border, border_length) = get_border(&instructions); + Some(get_area(&border, border_length)) +} + +aoc::solution!(18); + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_part_one() { + assert_eq!( + part_one(&aoc::template::read_file("examples", 18)), + Some(62) + ); + } + #[test] + fn test_part_two() { + assert_eq!( + part_two(&aoc::template::read_file("examples", 18)), + Some(952408144115) + ); + } +}