This commit is contained in:
Markus Brueckner 2024-12-14 19:45:22 +01:00
parent e291f48c84
commit 11999d177e
4 changed files with 224 additions and 0 deletions

61
2024/day14/Cargo.lock generated Normal file
View file

@ -0,0 +1,61 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "day14"
version = "0.1.0"
dependencies = [
"lazy_static",
"regex",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"

8
2024/day14/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "day14"
version = "0.1.0"
edition = "2021"
[dependencies]
lazy_static = "1.5.0"
regex = "1.11.1"

18
2024/day14/README.md Normal file
View file

@ -0,0 +1,18 @@
# Solution Day 14
Again one of those I couldn't do without inspiration. Reading the input is easily enough with the regex crate.
Simulating the robots is also very easy, although I got it wrong in the first try. I initially thought `x %= WIDTH`
is enough to bring `x` back into the allowed range, completely forgetting about negative numbers. After fixing
this issue, the task is actually easy enough.
Task 2, however... "form the shape of a Christmas tree"... What? I didn't even have an idea what this would look like.
Just the outer edge? Filled? Centered? What means "most" line up to form the Christmas tree? I was at a complete
loss. Some inspiration from Reddit later, I assumed, that the Christmas tree might form an outline, centered around
`x = 50`, so I calculated the number of robots, where the distance from the center column was roughly half the
distance from the top of the map (basically any robot, that roughly lies on one of two lines falling
away from the center column). Didn't work at all. Not within the first 100,000 seconds.
After some more digging someone suggested, that the safety factor from task 1 might actually be the key. They
proposed to calculate the safety factor for each second and render the map every time a new minimum occured. This
assumed, that the robots would cluster together, leaving large parts of the map empty and therefore have a very
low safety factor. This does actually work. After a few false positives, the tree emerges in a rendered map.

137
2024/day14/src/main.rs Normal file
View file

@ -0,0 +1,137 @@
use std::{
i32,
io::{stdin, Read},
};
use lazy_static::lazy_static;
use regex::Regex;
struct Robot {
x: i32,
y: i32,
v_x: i32,
v_y: i32,
}
const WIDTH: i32 = 101;
const HEIGHT: i32 = 103;
impl Robot {
fn step(&mut self) {
self.x += self.v_x;
self.x %= WIDTH;
if self.x < 0 {
self.x += WIDTH;
};
self.y += self.v_y;
self.y %= HEIGHT;
if self.y < 0 {
self.y += HEIGHT;
}
}
}
lazy_static! {
static ref ROBOT_REGEX: Regex = Regex::new(r"p=(\d+),(\d+)\s+v=([-\d]+),([-\d]+)").unwrap();
}
fn load_input() -> Vec<Robot> {
include_str!("../input.txt")
.lines()
.enumerate()
.map(|(idx, line)| {
let captures = ROBOT_REGEX
.captures(line)
.expect(format!("Failed to parse robot in line {idx}").as_str());
Robot {
x: captures
.get(1)
.and_then(|m| m.as_str().parse().ok())
.expect(format!("Couldn't parse x position in line {idx}").as_str()),
y: captures
.get(2)
.and_then(|m| m.as_str().parse().ok())
.expect(format!("Couldn't parse y position in line {idx}").as_str()),
v_x: captures
.get(3)
.and_then(|m| m.as_str().parse().ok())
.expect(format!("Couldn't parse x velocity in line {idx}").as_str()),
v_y: captures
.get(4)
.and_then(|m| m.as_str().parse().ok())
.expect(format!("Couldn't parse y velocity in line {idx}").as_str()),
}
})
.collect()
}
fn calculate_safety_factor(robots: &Vec<Robot>) -> i32 {
let (q1, q2, q3, q4) = robots.iter().fold((0, 0, 0, 0), |(q1, q2, q3, q4), robot| {
if robot.x < 50 {
if robot.y < 51 {
return (q1 + 1, q2, q3, q4);
}
if robot.y > 51 {
return (q1, q2 + 1, q3, q4);
}
}
if robot.x > 50 {
if robot.y < 51 {
return (q1, q2, q3 + 1, q4);
}
if robot.y > 51 {
return (q1, q2, q3, q4 + 1);
}
}
(q1, q2, q3, q4)
});
q1 * q2 * q3 * q4
}
fn task1() {
let mut robots = load_input();
for i in 0..100 {
robots.iter_mut().for_each(|r| r.step());
}
println!(
"Task 1: safety factor: {}",
calculate_safety_factor(&robots)
);
}
fn render_map(robots: &Vec<Robot>) {
for y in 0..HEIGHT {
for x in 0..WIDTH {
if robots.iter().any(|r| r.x == x && r.y == y) {
print!("#");
} else {
print!(".");
}
}
println!("");
}
}
fn task2() {
let mut robots = load_input();
let mut min_safety = i32::MAX;
for sec in 1..1000000 {
robots.iter_mut().for_each(|r| r.step());
let safety = calculate_safety_factor(&robots);
if safety < min_safety {
min_safety = safety;
render_map(&robots);
println!("Seconds: {sec}");
stdin().read(&mut [0u8]).unwrap();
}
}
}
fn main() {
task1();
task2();
}