]> localhost Git - adventofcode2024.git/commitdiff
Complete day 14
authorJack Kinsey <j.jameskinsey@gmail.com>
Tue, 17 Dec 2024 03:17:47 +0000 (22:17 -0500)
committerJack Kinsey <j.jameskinsey@gmail.com>
Tue, 17 Dec 2024 03:18:08 +0000 (22:18 -0500)
src/day14.rs [new file with mode: 0644]
src/main.rs

diff --git a/src/day14.rs b/src/day14.rs
new file mode 100644 (file)
index 0000000..4df7d37
--- /dev/null
@@ -0,0 +1,256 @@
+use regex::Regex;
+use std::io::Write;
+use std::ops::{Add, Rem, Sub};
+
+#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
+struct Point(i32, i32);
+
+impl Point {
+    fn scale(&self, multiplier: i32) -> Point {
+        Point(self.0 * multiplier, self.1 * multiplier)
+    }
+}
+
+impl Add for Point {
+    type Output = Point;
+
+    fn add(self, rhs: Self) -> Self::Output {
+        Point(self.0 + rhs.0, self.1 + rhs.1)
+    }
+}
+
+impl Sub for Point {
+    type Output = Point;
+
+    fn sub(self, rhs: Self) -> Self::Output {
+        Point(self.0 - rhs.0, self.1 - rhs.1)
+    }
+}
+
+impl Rem for Point {
+    type Output = Point;
+
+    fn rem(self, rhs: Self) -> Self::Output {
+        Point(self.0.rem_euclid(rhs.0), self.1.rem_euclid(rhs.1))
+    }
+}
+
+fn parse(input: &str) -> Vec<(Point, Point)> {
+    Regex::new(r"p=(-?\d+),(-?\d+) v=(-?\d+),(-?\d+)\n")
+        .unwrap()
+        .captures_iter(input)
+        .map(|c| c.extract())
+        .map(|(_, [px, py, vx, vy])| {
+            (
+                Point(px.parse().unwrap(), py.parse().unwrap()),
+                Point(vx.parse().unwrap(), vy.parse().unwrap()),
+            )
+        })
+        .collect()
+}
+
+fn trace(pos: Point, vel: Point, bounds: Point, count: i32) -> Point {
+    (pos + vel.scale(count)) % bounds
+}
+
+fn quadrantize(pos: &[Point], bounds: Point) -> [Vec<Point>; 4] {
+    let cx = (bounds.0 - 1) / 2;
+    let cy = (bounds.1 - 1) / 2;
+    let mut quads = [vec![], vec![], vec![], vec![]];
+    for p in pos.iter().copied() {
+        match p {
+            Point(x, y) if x > cx && y < cy => quads[0].push(p),
+            Point(x, y) if x < cx && y < cy => quads[1].push(p),
+            Point(x, y) if x < cx && y > cy => quads[2].push(p),
+            Point(x, y) if x > cx && y > cy => quads[3].push(p),
+            _ => (),
+        }
+    }
+    quads
+}
+
+fn safety_factor(pos: &[Point], bounds: Point) -> u64 {
+    quadrantize(pos, bounds)
+        .iter()
+        .map(|pos| pos.len())
+        .product::<usize>()
+        .try_into()
+        .unwrap()
+}
+
+fn render(pos: &[Point], bounds: Point) -> String {
+    let mut vis: Vec<_> = std::iter::repeat_with(|| vec!['.'; bounds.0.try_into().unwrap()])
+        .take((bounds.1 + 1).try_into().unwrap())
+        .collect();
+    for p in pos {
+        let x: usize = p.0.try_into().unwrap();
+        let y: usize = p.1.try_into().unwrap();
+        vis[y][x] = '@';
+    }
+    let mut ret = String::new();
+    for v in vis {
+        for c in v {
+            ret.push(c);
+        }
+        ret.push('\n');
+    }
+    ret
+}
+
+fn shopping(robots: &[(Point, Point)], bounds: Point) -> Result<(), std::io::Error> {
+    // i checked how many unique positions there are manually
+    for i in 0..10403 {
+        let pos: Vec<_> = robots
+            .iter()
+            .map(|&(p, v)| trace(p, v, bounds, i))
+            .collect();
+        let mut file = std::fs::File::create(format!("output/{:04}.txt", i))?;
+        file.write_all(render(&pos, bounds).as_bytes())?;
+        // once they're all written you can compress every file and look for the smallest since
+        // it's lowest-entropy, e.g., `gzip *.txt && du -b * | sort -n | head -n 10`
+        // reddit pointed this general strategy out, originally i just printed them all and slept
+        // between each lol: `std::thread::sleep(std::time::Duration::from_millis(50))`
+    }
+    Ok(())
+}
+
+pub fn part1(input: &str) -> u64 {
+    let bounds = Point(101, 103);
+    let pos: Vec<_> = parse(input)
+        .iter()
+        .map(|&(p, v)| trace(p, v, bounds, 100))
+        .collect();
+    safety_factor(&pos, bounds)
+}
+
+pub fn part2(input: &str) -> u64 {
+    let bounds = Point(101, 103);
+    let robots = parse(input);
+    if false {
+        shopping(&robots, bounds).unwrap();
+    }
+    // in case you want to see him :-)
+    if false {
+        let pos: Vec<_> = robots
+            .iter()
+            .map(|&(p, v)| trace(p, v, bounds, 7083))
+            .collect();
+        println!("{}", render(&pos, bounds));
+    }
+    7083
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    const INPUT_STR: &str = concat!(
+        "p=0,4 v=3,-3\n",
+        "p=6,3 v=-1,-3\n",
+        "p=10,3 v=-1,2\n",
+        "p=2,0 v=2,-1\n",
+        "p=0,0 v=1,3\n",
+        "p=3,0 v=-2,-2\n",
+        "p=7,6 v=-1,-3\n",
+        "p=3,0 v=-1,-2\n",
+        "p=9,3 v=2,3\n",
+        "p=7,3 v=-1,2\n",
+        "p=2,4 v=2,-3\n",
+        "p=9,5 v=-3,-3\n",
+    );
+
+    #[test]
+    fn test_parse() {
+        assert_eq!(
+            parse(INPUT_STR),
+            [
+                (Point(0, 4), Point(3, -3)),
+                (Point(6, 3), Point(-1, -3)),
+                (Point(10, 3), Point(-1, 2)),
+                (Point(2, 0), Point(2, -1)),
+                (Point(0, 0), Point(1, 3)),
+                (Point(3, 0), Point(-2, -2)),
+                (Point(7, 6), Point(-1, -3)),
+                (Point(3, 0), Point(-1, -2)),
+                (Point(9, 3), Point(2, 3)),
+                (Point(7, 3), Point(-1, 2)),
+                (Point(2, 4), Point(2, -3)),
+                (Point(9, 5), Point(-3, -3))
+            ]
+        )
+    }
+
+    #[test]
+    fn test_trace() {
+        let bounds = Point(11, 7);
+
+        let pos = Point(2, 4);
+        let vel = Point(2, -3);
+        assert_eq!(trace(pos, vel, bounds, 0), Point(2, 4));
+        assert_eq!(trace(pos, vel, bounds, 1), Point(4, 1));
+        assert_eq!(trace(pos, vel, bounds, 2), Point(6, 5));
+        assert_eq!(trace(pos, vel, bounds, 3), Point(8, 2));
+        assert_eq!(trace(pos, vel, bounds, 4), Point(10, 6));
+        assert_eq!(trace(pos, vel, bounds, 5), Point(1, 3));
+
+        let tick = 100;
+        assert_eq!(
+            parse(INPUT_STR)
+                .iter()
+                .map(|&(p, v)| trace(p, v, bounds, tick))
+                .collect::<Vec<_>>(),
+            [
+                Point(3, 5),
+                Point(5, 4),
+                Point(9, 0),
+                Point(4, 5),
+                Point(1, 6),
+                Point(1, 3),
+                Point(6, 0),
+                Point(2, 3),
+                Point(0, 2),
+                Point(6, 0),
+                Point(4, 5),
+                Point(6, 6),
+            ]
+        );
+    }
+
+    #[test]
+    fn test_quandrantize() {
+        let bounds = Point(11, 7);
+        let tick = 100;
+        let pos: Vec<_> = parse(INPUT_STR)
+            .iter()
+            .map(|&(p, v)| trace(p, v, bounds, tick))
+            .collect();
+        assert_eq!(
+            quadrantize(&pos, bounds)
+                .iter()
+                .map(|q| q.len())
+                .collect::<Vec<_>>(),
+            [3, 1, 4, 1]
+        );
+    }
+
+    #[test]
+    fn test_safety_factor() {
+        let bounds = Point(11, 7);
+        let tick = 100;
+        let pos: Vec<_> = parse(INPUT_STR)
+            .iter()
+            .map(|&(p, v)| trace(p, v, bounds, tick))
+            .collect();
+        assert_eq!(safety_factor(&pos, bounds), 12);
+    }
+
+    #[test]
+    fn test_part1() {
+        assert_eq!(part1(&crate::input(14).unwrap()), 224357412)
+    }
+
+    #[test]
+    fn test_part2() {
+        assert_eq!(part2(&crate::input(14).unwrap()), 7083)
+    }
+}
index 7c8266339b97d5593b5556048ddc303dbf05e9f8..11b28352b69b4739bcd66c7c81526ffc7bed83f5 100644 (file)
@@ -11,8 +11,8 @@ pub mod day10;
 pub mod day11;
 pub mod day12;
 pub mod day13;
-// pub mod day14;
 // pub mod day15;
+pub mod day14;
 // pub mod day16;
 // pub mod day17;
 // pub mod day18;
@@ -40,8 +40,8 @@ const DAYS: &[(Part, Part)] = &[
     (day11::part1, day11::part2),
     (day12::part1, day12::part2),
     (day13::part1, day13::part2),
-    // (day14::part1, day14::part2),
     // (day15::part1, day15::part2),
+    (day14::part1, day14::part2),
     // (day16::part1, day16::part2),
     // (day17::part1, day17::part2),
     // (day18::part1, day18::part2),