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"
|
||||
path = "src/day08/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "day09"
|
||||
path = "src/day09/main.rs"
|
||||
|
||||
[dependencies]
|
||||
# Required for one of the day06 solutions (commented out)
|
||||
# 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