Day 15: Warehouse Woes

Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

FAQ

You are viewing a single thread.
View all comments
2 points

Rust

Part 2 was a bit tricky. Moving into a box horizontally works mostly the same as for part 1, for the vertical case I used two recursive functions. The first recurses from the left and right side for each box just to find out if the entire tree can be moved. The second function actually does the moving in a similar recursive structure, but now with the knowledge that all subtrees can actually be moved.

Lots of moving parts, but at least it could very nicely be debugged by printing out the map from the two minimal examples after each round.

Solution
use euclid::{default::*, vec2};

// Common type for both parts. In part 1 all boxes are BoxL.
#[derive(Clone, Copy)]
enum Spot {
    Empty,
    BoxL,
    BoxR,
    Wall,
}

impl From<u8> for Spot {
    fn from(value: u8) -> Self {
        match value {
            b'.' | b'@' => Spot::Empty,
            b'O' => Spot::BoxL,
            b'#' => Spot::Wall,
            other => panic!("Invalid spot: {other}"),
        }
    }
}

fn parse(input: &str) -> (Vec<Vec<Spot>>, Point2D<i32>, Vec<Vector2D<i32>>) {
    let (field_s, moves_s) = input.split_once("\n\n").unwrap();
    let mut field = Vec::new();
    let mut robot = None;
    for (y, l) in field_s.lines().enumerate() {
        let mut row = Vec::new();
        for (x, b) in l.bytes().enumerate() {
            row.push(Spot::from(b));
            if b == b'@' {
                robot = Some(Point2D::new(x, y).to_i32())
            }
        }
        field.push(row);
    }

    let moves = moves_s
        .bytes()
        .filter(|b| *b != b'\n')
        .map(|b| match b {
            b'^' => vec2(0, -1),
            b'>' => vec2(1, 0),
            b'v' => vec2(0, 1),
            b'<' => vec2(-1, 0),
            other => panic!("Invalid move: {other}"),
        })
        .collect();
    (field, robot.unwrap(), moves)
}

fn gps(field: &[Vec<Spot>]) -> u32 {
    let mut sum = 0;
    for (y, row) in field.iter().enumerate() {
        for (x, s) in row.iter().enumerate() {
            if let Spot::BoxL = s {
                sum += x + 100 * y;
            }
        }
    }
    sum as u32
}

fn part1(input: String) {
    let (mut field, mut robot, moves) = parse(&input);
    for m in moves {
        let next = robot + m;
        match field[next.y as usize][next.x as usize] {
            Spot::Empty => robot = next, // Move into space
            Spot::BoxL => {
                let mut search = next + m;
                let can_move = loop {
                    match field[search.y as usize][search.x as usize] {
                        Spot::BoxL => {}
                        Spot::Wall => break false,
                        Spot::Empty => break true,
                        Spot::BoxR => unreachable!(),
                    }
                    search += m;
                };
                if can_move {
                    robot = next;
                    field[next.y as usize][next.x as usize] = Spot::Empty;
                    field[search.y as usize][search.x as usize] = Spot::BoxL;
                }
            }
            Spot::Wall => {} // Cannot move
            Spot::BoxR => unreachable!(),
        }
    }
    println!("{}", gps(&field));
}

// Transform part 1 field to wider part 2 field
fn widen(field: &[Vec<Spot>]) -> Vec<Vec<Spot>> {
    field
        .iter()
        .map(|row| {
            row.iter()
                .flat_map(|s| match s {
                    Spot::Empty => [Spot::Empty; 2],
                    Spot::Wall => [Spot::Wall; 2],
                    Spot::BoxL => [Spot::BoxL, Spot::BoxR],
                    Spot::BoxR => unreachable!(),
                })
                .collect()
        })
        .collect()
}

// Recursively find out whether or not the robot can move in direction `dir` from `start`.
fn can_move_rec(field: &[Vec<Spot>], start: Point2D<i32>, dir: Vector2D<i32>) -> bool {
    let next = start + dir;
    match field[next.y as usize][next.x as usize] {
        Spot::Empty => true,
        Spot::BoxL => can_move_rec(field, next, dir) && can_move_rec(field, next + vec2(1, 0), dir),
        Spot::BoxR => can_move_rec(field, next - vec2(1, 0), dir) && can_move_rec(field, next, dir),
        Spot::Wall => false,
    }
}

// Recursively execute a move for vertical directions into boxes.
fn do_move(field: &mut [Vec<Spot>], start: Point2D<i32>, dir: Vector2D<i32>) {
    let next = start + dir;
    match field[next.y as usize][next.x as usize] {
        Spot::Empty | Spot::Wall => {}
        Spot::BoxL => {
            do_move(field, next, dir);
            do_move(field, next + vec2(1, 0), dir);
            let move_to = next + dir;
            field[next.y as usize][next.x as usize] = Spot::Empty;
            field[next.y as usize][next.x as usize + 1] = Spot::Empty;
            field[move_to.y as usize][move_to.x as usize] = Spot::BoxL;
            field[move_to.y as usize][move_to.x as usize + 1] = Spot::BoxR;
        }
        Spot::BoxR => {
            do_move(field, next - vec2(1, 0), dir);
            do_move(field, next, dir);
            let move_to = next + dir;
            field[next.y as usize][next.x as usize - 1] = Spot::Empty;
            field[next.y as usize][next.x as usize] = Spot::Empty;
            field[move_to.y as usize][move_to.x as usize - 1] = Spot::BoxL;
            field[move_to.y as usize][move_to.x as usize] = Spot::BoxR;
        }
    }
}

fn part2(input: String) {
    let (field1, robot1, moves) = parse(&input);
    let mut field = widen(&field1);
    let mut robot = Point2D::new(robot1.x * 2, robot1.y);
    for m in moves {
        let next = robot + m;
        match field[next.y as usize][next.x as usize] {
            Spot::Empty => robot = next, // Move into space
            Spot::BoxL | Spot::BoxR if m.y == 0 => {
                let mut search = next + m;
                let can_move = loop {
                    match field[search.y as usize][search.x as usize] {
                        Spot::BoxL | Spot::BoxR => {}
                        Spot::Wall => break false,
                        Spot::Empty => break true,
                    }
                    search += m;
                };
                if can_move {
                    robot = next;
                    // Shift boxes by array remove/insert
                    field[next.y as usize].remove(search.x as usize);
                    field[next.y as usize].insert(next.x as usize, Spot::Empty);
                }
            }
            Spot::BoxL | Spot::BoxR => {
                if can_move_rec(&field, robot, m) {
                    do_move(&mut field, robot, m);
                    robot = next;
                }
            }
            Spot::Wall => {} // Cannot move
        }
    }
    println!("{}", gps(&field));
}

util::aoc_main!();

Also on github

permalink
report
reply

Advent Of Code

!advent_of_code@programming.dev

Create post

An unofficial home for the advent of code community on programming.dev!

Advent of Code is an annual Advent calendar of small programming puzzles for a variety of skill sets and skill levels that can be solved in any programming language you like.

AoC 2024

Solution Threads

M T W T F S S
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25

Rules/Guidelines

  • Follow the programming.dev instance rules
  • Keep all content related to advent of code in some way
  • If what youre posting relates to a day, put in brackets the year and then day number in front of the post title (e.g. [2024 Day 10])
  • When an event is running, keep solutions in the solution megathread to avoid the community getting spammed with posts

Relevant Communities

Relevant Links

Credits

Icon base by Lorc under CC BY 3.0 with modifications to add a gradient

console.log('Hello World')

Community stats

  • 467

    Monthly active users

  • 111

    Posts

  • 1.1K

    Comments