Rust Day 9
This commit is contained in:
parent
a8cd0b0688
commit
7e9e3e63c3
6 changed files with 2211 additions and 0 deletions
|
@ -55,6 +55,10 @@ path = "src/day07/main.rs"
|
||||||
name = "day08"
|
name = "day08"
|
||||||
path = "src/day08/main.rs"
|
path = "src/day08/main.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "day09"
|
||||||
|
path = "src/day09/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Required for one of the day06 solutions (commented out)
|
# Required for one of the day06 solutions (commented out)
|
||||||
# phf = { version = "0.11.1", features = ["macros"] }
|
# phf = { version = "0.11.1", features = ["macros"] }
|
2000
rust/src/day09/input.txt
Normal file
2000
rust/src/day09/input.txt
Normal file
File diff suppressed because it is too large
Load diff
62
rust/src/day09/main.rs
Normal file
62
rust/src/day09/main.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
#![feature(exclusive_range_pattern)]
|
||||||
|
const INPUT: &str = include_str!("input.txt");
|
||||||
|
|
||||||
|
mod part_1;
|
||||||
|
use part_1::part_1;
|
||||||
|
mod part_2;
|
||||||
|
use part_2::part_2;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub(crate) enum Direction {
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Direction> for (i64, i64) {
|
||||||
|
fn from(value: Direction) -> Self {
|
||||||
|
match value {
|
||||||
|
Direction::Up => (0, 1),
|
||||||
|
Direction::Down => (0, -1),
|
||||||
|
Direction::Left => (-1, 0),
|
||||||
|
Direction::Right => (1, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Move(Direction, i64);
|
||||||
|
|
||||||
|
impl Move {
|
||||||
|
fn from_string(string: &'static str) -> Self {
|
||||||
|
let (direction, distance) = string.split_at(2);
|
||||||
|
let direction = match direction.chars().next().unwrap() {
|
||||||
|
'U' => Direction::Up,
|
||||||
|
'D' => Direction::Down,
|
||||||
|
'L' => Direction::Left,
|
||||||
|
'R' => Direction::Right,
|
||||||
|
_ => panic!("Non-direction character found"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Self(
|
||||||
|
direction,
|
||||||
|
distance
|
||||||
|
.parse()
|
||||||
|
.unwrap_or_else(|_| panic!("Found non-numeric input {distance}")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn distance_between_points(point_a: &(i64, i64), point_b: &(i64, i64)) -> (i64, i64) {
|
||||||
|
(point_a.0 - point_b.0, point_a.1 - point_b.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_input(input: &'static str) -> Vec<Move> {
|
||||||
|
input.lines().map(Move::from_string).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let input = parse_input(INPUT);
|
||||||
|
part_1(&input);
|
||||||
|
part_2(&input);
|
||||||
|
}
|
55
rust/src/day09/part_1.rs
Normal file
55
rust/src/day09/part_1.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use crate::Move;
|
||||||
|
|
||||||
|
pub(crate) fn part_1(input: &Vec<Move>) -> usize {
|
||||||
|
let mut head_position = (0i64, 0i64);
|
||||||
|
let mut tail_position = (0i64, 0i64);
|
||||||
|
let mut visited_tail_positions = HashSet::new();
|
||||||
|
|
||||||
|
for head_movement in input.iter() {
|
||||||
|
for _ in 0..head_movement.1 {
|
||||||
|
match head_movement.0 {
|
||||||
|
crate::Direction::Up => head_position.1 += 1,
|
||||||
|
crate::Direction::Down => head_position.1 -= 1,
|
||||||
|
crate::Direction::Right => head_position.0 += 1,
|
||||||
|
crate::Direction::Left => head_position.0 -= 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
let point_distance = crate::distance_between_points(&head_position, &tail_position);
|
||||||
|
|
||||||
|
match point_distance {
|
||||||
|
(2, 0) => tail_position.0 += 1,
|
||||||
|
(-2, 0) => tail_position.0 -= 1,
|
||||||
|
(0, 2) => tail_position.1 += 1,
|
||||||
|
(0, -2) => tail_position.1 -= 1,
|
||||||
|
(_, 2) => tail_position = (head_position.0, tail_position.1 + 1),
|
||||||
|
(2, _) => tail_position = (tail_position.0 + 1, head_position.1),
|
||||||
|
(_, -2) => tail_position = (head_position.0, tail_position.1 - 1),
|
||||||
|
(-2, _) => tail_position = (tail_position.0 - 1, head_position.1),
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
visited_tail_positions.insert(tail_position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Part 1: {:?}", visited_tail_positions.len());
|
||||||
|
|
||||||
|
visited_tail_positions.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
const SAMPLE_INPUT: &str = include_str!("sample_input.txt");
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_solution() {
|
||||||
|
assert_eq!(super::part_1(&crate::parse_input(crate::INPUT)), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_sample_solution() {
|
||||||
|
assert_eq!(super::part_1(&crate::parse_input(SAMPLE_INPUT)), 13);
|
||||||
|
}
|
||||||
|
}
|
82
rust/src/day09/part_2.rs
Normal file
82
rust/src/day09/part_2.rs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use crate::Move;
|
||||||
|
const TAIL_LENGTH: usize = 9;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct Rope<const N: usize = 1> {
|
||||||
|
head: (i64, i64),
|
||||||
|
tails: [(i64, i64); N],
|
||||||
|
tail_history: HashSet<(i64, i64)>,
|
||||||
|
}
|
||||||
|
impl<const N: usize> Rope<N> {
|
||||||
|
fn new() -> Self {
|
||||||
|
Rope {
|
||||||
|
head: (0, 0),
|
||||||
|
tails: [(0, 0); N],
|
||||||
|
tail_history: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn pos_diff(&self, index: usize) -> (i64, i64) {
|
||||||
|
let head = if index == 0 {
|
||||||
|
self.head
|
||||||
|
} else {
|
||||||
|
self.tails[index - 1]
|
||||||
|
};
|
||||||
|
let tail = self.tails[index];
|
||||||
|
(tail.0 - head.0, tail.1 - head.1)
|
||||||
|
}
|
||||||
|
fn execute(&mut self, Move(dir, count): &Move) {
|
||||||
|
let dir = <(i64, i64)>::from(*dir);
|
||||||
|
for _ in 0..*count {
|
||||||
|
self.head.0 += dir.0;
|
||||||
|
self.head.1 += dir.1;
|
||||||
|
|
||||||
|
for i in 0..N {
|
||||||
|
let tail_movement = tail_update(self.pos_diff(i));
|
||||||
|
self.tails[i].0 += tail_movement.0;
|
||||||
|
self.tails[i].1 += tail_movement.1;
|
||||||
|
}
|
||||||
|
self.tail_history.insert(self.tails[N - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tail_update(diff: (i64, i64)) -> (i64, i64) {
|
||||||
|
let reaction = (-diff.0.signum(), -diff.1.signum());
|
||||||
|
let distance = diff.0.abs() + diff.1.abs();
|
||||||
|
let has_zero_component = diff.0.abs() == 0 || diff.1.abs() == 0;
|
||||||
|
match (has_zero_component, distance) {
|
||||||
|
(true, 2) | (_, 3..) => reaction,
|
||||||
|
_ => (0, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn part_2(input: &Vec<Move>) -> usize {
|
||||||
|
let mut rope = Rope::<TAIL_LENGTH>::new();
|
||||||
|
|
||||||
|
for head_movement in input {
|
||||||
|
rope.execute(head_movement);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = rope.tail_history.len();
|
||||||
|
|
||||||
|
println!("Part 2: {result}");
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
const SAMPLE_INPUT: &str = include_str!("sample_input.txt");
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_solution() {
|
||||||
|
assert_eq!(super::part_2(&crate::parse_input(crate::INPUT)), 2458);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_with_sample_solution() {
|
||||||
|
assert_eq!(super::part_2(&crate::parse_input(SAMPLE_INPUT)), 1);
|
||||||
|
}
|
||||||
|
}
|
8
rust/src/day09/sample_input.txt
Normal file
8
rust/src/day09/sample_input.txt
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
R 4
|
||||||
|
U 4
|
||||||
|
L 3
|
||||||
|
D 1
|
||||||
|
R 4
|
||||||
|
D 1
|
||||||
|
L 5
|
||||||
|
R 2
|
Loading…
Reference in a new issue