Skip to content

Advent of Code Day Five - Supply Stack

December 7, 2022 | 12:26 PM

Camp Cleanup

In Day 5 we are helping the elves figure out which crates will be on top whne the crane unloads them. The most difficult part of this was parsing the input

Part 1

We are given the test input which represents the current layout of the crates in their stacks and the instructions for unloading them.

    [D]
[N] [C]
[Z] [M] [P]
 1   2   3

move 1 from 2 to 1
move 3 from 1 to 3
move 2 from 2 to 1
move 1 from 1 to 2

The first step as always is to read in the data from our file.

fn main() {
    let input: &str = include_str!("./day5.prod");
/* ... */
}

I figured the best way to handle this was to parse the input into structs or types. So we will have a collection of Stacks, which are themselves a collection of Crates, which are just characters.

pub struct Procedure {
    num_of_containers: usize,
    from_stack: usize,
    to_stack: usize,
}

type Stack = Vec<Crate>;
type Crate = char;

Next in the parsing logic is to split the Crate map (the schema) from the instructions. We add those to a tuple varaible so we can process them later.

let (schema, procedures) = input.split_once("\n\n").unwrap();

Parse the Schema

Here we collect the input into a collection and call it graph. From this we can determine how many stacks there are and then we can load the data from this. It is important to note that the container is actually the character inside the square brackets. So effectivly each container is 4 characters long when you count the trailing whitespace ([x] ).

let graph: Vec<_> = schema.lines().collect();
let graph_len = graph[0].len() / 4 + 1;

Now we can create the collection of stacks to iterate over and parse the chars for each of the crates into a container and then push the container onto the stack filtering out the whitespace as we go along.

let mut stacks: Vec<Stack> = vec![Vec::with_capacity(graph.len() - 1); graph_len + 1];

for row_number in (0..graph.len() - 1).rev() {
    let row = graph[row_number];
    for (stack_number, stack) in stacks.iter_mut().enumerate().skip(1).take(graph_len) {
        let container = row.chars().nth((stack_number - 1) * 4 + 1).unwrap();

        if !container.is_ascii_whitespace() {
            stack.push(container);
        }
    }
}

Parsing the Instructions

Next we need to parse the procedures so we can follow the steps and get the final answer. I took the procedures portion of the tuple from the split on the double newline and used the new and handy map function to process each step. In that I split the step on the whitespace characters and then filter mapped that to save the numer if the character(s) can be parsed to a number. then those numbers are saved to a collection. I then populate an instance of the Procedure struct and then we collect them all into a Vector.

let instructions = procedures
    .lines()
    .map(|step| {
        let mut numbers = step
            .split_ascii_whitespace()
            .filter_map(|token| token.parse().ok());

        Procedure {
            num_of_containers: numbers.next().unwrap(),
            from_stack: numbers.next().unwrap(),
            to_stack: numbers.next().unwrap(),
        }
    })
    .collect();

Doin’ the Work

Now that we have parsed the data we can finally start processing it.

pub fn part_one((in_stacks, instructions): &(Vec<Stack>, Vec<Procedure>)) -> String {
    let mut stacks = in_stacks.to_owned();

    for i in instructions {
        for _x in 0..i.num_of_containers {
            let supply_crate = stacks[i.from_stack].pop().unwrap();
            stacks[i.to_stack].push(supply_crate);
        }
    }

    return stacks.iter().filter_map(|s| s.iter().last()).collect();
}

Part 2

Since the parsing work is done, we jsut need a new method for part two, which handles the sorting in a differnt way.

pub fn part_two((in_stacks, instructions): &(Vec<Stack>, Vec<Procedure>)) -> String {
    let mut stacks = in_stacks.to_owned();

    for i in instructions {
        let from_stack = &mut stacks[i.from_stack];
        let mut moved_crates = from_stack.split_off(from_stack.len() - i.num_of_containers);
        stacks[i.to_stack].append(&mut moved_crates);
    }
    return stacks.iter().filter_map(|s|s.iter().last()).collect();
}

The Final Solution

fn main() {
    let input = include_str!("./day5.prod");

    let parsed_input = parse_input(input);
    let p1_result = part_one(&parsed_input);
    println!("Result: {}", p1_result);
    let p2_result = part_two(&parsed_input);
    println!("Result: {}", p2_result);

}

pub struct Procedure {
    num_of_containers: usize,
    from_stack: usize,
    to_stack: usize,
}

type Stack = Vec<Crate>;
type Crate = char;

pub fn part_two((in_stacks, instructions): &(Vec<Stack>, Vec<Procedure>)) -> String {
    let mut stacks = in_stacks.to_owned();

    for i in instructions {
        let from_stack = &mut stacks[i.from_stack];
        let mut moved_crates = from_stack.split_off(from_stack.len() - i.num_of_containers);
        stacks[i.to_stack].append(&mut moved_crates);
    }
    return stacks.iter().filter_map(|s|s.iter().last()).collect();
}

pub fn part_one((in_stacks, instructions): &(Vec<Stack>, Vec<Procedure>)) -> String {
    let mut stacks = in_stacks.to_owned();

    for i in instructions {
        for _x in 0..i.num_of_containers {
            let supply_crate = stacks[i.from_stack].pop().unwrap();
            stacks[i.to_stack].push(supply_crate);
        }
    }

    return stacks.iter().filter_map(|s| s.iter().last()).collect();
}

pub fn parse_input(input: &'static str) -> (Vec<Stack>, Vec<Procedure>) {
    //break input into two parts
    let (schema, procedures) = input.split_once("\n\n").unwrap();

    //get the size of the cargo area
    let graph: Vec<_> = schema.lines().collect();
    //each container is 4 char long wiht the trailing space e.g. '[X] '
    //This gets the count of stacks from the number of cahr in a row
    let graph_len = graph[0].len() / 4 + 1;
    println!("graph_len: {}", graph_len);

    //load the stacks from input
    let mut stacks: Vec<Stack> = vec![Vec::with_capacity(graph.len() - 1); graph_len + 1];

    for row_number in (0..graph.len() - 1).rev() {
        let row = graph[row_number];
        for (stack_number, stack) in stacks.iter_mut().enumerate().skip(1).take(graph_len) {
            let container = row.chars().nth((stack_number - 1) * 4 + 1).unwrap();

            if !container.is_ascii_whitespace() {
                stack.push(container);
            }
        }
    }

    let instructions = procedures
        .lines()
        .map(|step| {
            let mut numbers = step
                .split_ascii_whitespace()
                .filter_map(|token| token.parse().ok());

            Procedure {
                num_of_containers: numbers.next().unwrap(),
                from_stack: numbers.next().unwrap(),
                to_stack: numbers.next().unwrap(),
            }
        })
        .collect();

    return (stacks, instructions);
}