solution: day 18

This commit is contained in:
Matej Janezic 2023-12-18 21:02:50 +01:00
parent 0000033086
commit 0000034077
Signed by: janezicmatej
GPG Key ID: 4298E230ED37B2C0
2 changed files with 140 additions and 0 deletions

14
data/examples/18.txt Normal file
View File

@ -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)

126
src/bin/18.rs Normal file
View File

@ -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<Self, Self::Err> {
use Direction::*;
Ok(match s {
"U" | "3" => Up,
"D" | "1" => Down,
"L" | "2" => Left,
"R" | "0" => Right,
_ => return Err(ParseDirectionError),
})
}
}
impl From<Direction> 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<isize> {
let instructions: Vec<(Direction, isize)> = input
.lines()
.map(|line| {
let [d, l, _] = line.splitn(3, ' ').collect::<Vec<_>>().try_into().unwrap();
(d.parse().unwrap(), l.parse::<isize>().unwrap())
})
.collect();
let (border, border_length) = get_border(&instructions);
Some(get_area(&border, border_length))
}
fn join_option_tuple<T, U>((a, b): (Option<T>, Option<U>)) -> Option<(T, U)> {
Some((a?, b?))
}
pub fn part_two(input: &str) -> Option<isize> {
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)
);
}
}