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() {
|
||||
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
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