Solve day09

This commit is contained in:
Malte Tammena 2022-12-09 07:16:58 +01:00
parent 7bf09540c4
commit a3f17cd2b7
4 changed files with 2184 additions and 2 deletions

2000
day09/input Normal file

File diff suppressed because it is too large Load diff

8
day09/input.test Normal file
View file

@ -0,0 +1,8 @@
R 4
U 4
L 3
D 1
R 4
D 1
L 5
R 2

View file

@ -1,3 +1,62 @@
fn main() {
println!("Hello, world!");
use std::{
fs::File,
io::{BufRead, BufReader},
};
use positions::{Dir, Rope};
mod positions;
#[derive(Debug, Default, PartialEq, Eq)]
enum SolvePuzzle {
First,
#[default]
Second,
}
fn solve_puzzle<const N: usize, R: BufRead>(reader: R) -> usize {
let lines = reader
.lines()
.map(Result::unwrap)
.flat_map(Dir::parse_line)
.collect::<Rope<N>>();
lines.visited.len()
}
fn main() {
let mut args = ::std::env::args().skip(1);
let file = args.next().unwrap_or_else(|| String::from("./input"));
let solve = args
.next()
.map(|arg| match arg.as_str() {
"first" => SolvePuzzle::First,
"second" => SolvePuzzle::Second,
_ => unreachable!(),
})
.unwrap_or_default();
let reader = BufReader::new(File::open(file).expect("Opening file"));
let solution = match solve {
SolvePuzzle::First => solve_puzzle::<2, _>(reader),
SolvePuzzle::Second => solve_puzzle::<10, _>(reader),
};
println!("{}", solution);
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::*;
#[test]
fn example_on_first() {
let reader = Cursor::new(include_str!("../input.test"));
assert_eq!(solve_puzzle::<2, _>(reader), 13);
}
#[test]
fn example_on_second() {
let reader = Cursor::new(include_str!("../input.test"));
assert_eq!(solve_puzzle::<10, _>(reader), 1);
}
}

115
day09/src/positions.rs Normal file
View file

@ -0,0 +1,115 @@
use std::{collections::HashSet, ops::AddAssign};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Dir {
Left,
Right,
Up,
Down,
}
#[derive(Debug)]
pub struct Rope<const N: usize> {
pub visited: HashSet<(isize, isize)>,
knots: [(isize, isize); N],
}
impl<const N: usize> Rope<N> {
pub fn push(&mut self, dir: Dir) {
// Adjust the head
self.knots[0] += dir;
// Fix possibly wrong position of second element
self.verify_knot_position(1);
}
fn verify_knot_position(&mut self, knot: usize) {
let head = self.knots[knot - 1];
let tail = self.knots[knot];
let (x_off, y_off) = (head.0 - tail.0, head.1 - tail.1);
if x_off.abs() <= 1 && y_off.abs() <= 1 {
// everything is fine
return;
}
self.fix_knot_position(knot, x_off, y_off);
}
fn fix_knot_position(&mut self, knot: usize, x_off: isize, y_off: isize) {
let tail = &mut self.knots[knot];
match (x_off, y_off) {
(-2, 0) => *tail += Dir::Left,
(2, 0) => *tail += Dir::Right,
(0, -2) => *tail += Dir::Up,
(0, 2) => *tail += Dir::Down,
(x_off, y_off) if x_off < 0 && y_off < 0 => {
tail.0 -= 1;
tail.1 -= 1
}
(x_off, y_off) if x_off < 0 && y_off > 0 => {
tail.0 -= 1;
tail.1 += 1
}
(x_off, y_off) if x_off > 0 && y_off < 0 => {
tail.0 += 1;
tail.1 -= 1
}
(x_off, y_off) if x_off > 0 && y_off > 0 => {
tail.0 += 1;
tail.1 += 1
}
_ => unreachable!(),
}
if knot == N - 1 {
// Last knot, remember new position
self.visited.insert(*tail);
} else {
self.verify_knot_position(knot + 1)
}
}
}
impl Dir {
pub fn parse_line<S: AsRef<str>>(line: S) -> impl Iterator<Item = Self> {
let (dir, count) = line.as_ref().split_at(1);
// Remove leading whitespace
let count = &count[1..];
let dir = match dir {
"L" => Self::Left,
"R" => Self::Right,
"U" => Self::Up,
"D" => Self::Down,
_ => unreachable!(),
};
::std::iter::repeat(dir).take(count.parse().unwrap())
}
}
impl<const N: usize> FromIterator<Dir> for Rope<N> {
fn from_iter<T: IntoIterator<Item = Dir>>(iter: T) -> Self {
iter.into_iter().fold(Self::default(), |mut acc, dir| {
acc.push(dir);
acc
})
}
}
impl<const N: usize> Default for Rope<N> {
fn default() -> Self {
Self {
visited: [(0, 0)].into_iter().collect(),
knots: [(0, 0); N],
}
}
}
impl AddAssign<Dir> for (isize, isize) {
fn add_assign(&mut self, rhs: Dir) {
let (x_add, y_add) = match rhs {
Dir::Left => (-1, 0),
Dir::Right => (1, 0),
Dir::Up => (0, -1),
Dir::Down => (0, 1),
};
self.0 += x_add;
self.1 += y_add;
}
}