Solve day09
This commit is contained in:
parent
7bf09540c4
commit
a3f17cd2b7
2000
day09/input
Normal file
2000
day09/input
Normal file
File diff suppressed because it is too large
Load diff
8
day09/input.test
Normal file
8
day09/input.test
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
R 4
|
||||||
|
U 4
|
||||||
|
L 3
|
||||||
|
D 1
|
||||||
|
R 4
|
||||||
|
D 1
|
||||||
|
L 5
|
||||||
|
R 2
|
|
@ -1,3 +1,62 @@
|
||||||
fn main() {
|
use std::{
|
||||||
println!("Hello, world!");
|
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
115
day09/src/positions.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue