From 11999d177e40f8b713cdb8bc4c93b5c68234c183 Mon Sep 17 00:00:00 2001 From: Markus Brueckner Date: Sat, 14 Dec 2024 19:45:22 +0100 Subject: [PATCH] Day 14 --- 2024/day14/Cargo.lock | 61 ++++++++++++++++++ 2024/day14/Cargo.toml | 8 +++ 2024/day14/README.md | 18 ++++++ 2024/day14/src/main.rs | 137 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 224 insertions(+) create mode 100644 2024/day14/Cargo.lock create mode 100644 2024/day14/Cargo.toml create mode 100644 2024/day14/README.md create mode 100644 2024/day14/src/main.rs diff --git a/2024/day14/Cargo.lock b/2024/day14/Cargo.lock new file mode 100644 index 0000000..babad86 --- /dev/null +++ b/2024/day14/Cargo.lock @@ -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" diff --git a/2024/day14/Cargo.toml b/2024/day14/Cargo.toml new file mode 100644 index 0000000..e7dd85f --- /dev/null +++ b/2024/day14/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "day14" +version = "0.1.0" +edition = "2021" + +[dependencies] +lazy_static = "1.5.0" +regex = "1.11.1" diff --git a/2024/day14/README.md b/2024/day14/README.md new file mode 100644 index 0000000..c79d59a --- /dev/null +++ b/2024/day14/README.md @@ -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. \ No newline at end of file diff --git a/2024/day14/src/main.rs b/2024/day14/src/main.rs new file mode 100644 index 0000000..602894e --- /dev/null +++ b/2024/day14/src/main.rs @@ -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 { + 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) -> 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) { + 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(); +}