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.
- I created a method that takes in the tuple from parsing
(Vec<Stack>, Vec<Procedure>)
and returns the desiredString
result. - I first get a mutable instance of the stacks portion of the tuple.
- I then iterate over the instructions/procedures and for each instruction I loop across the numbe of containers provided by the instruction and pop the ‘top’ crate off of the ‘from’ stack and the push it to the ‘to’stack. I feel like this loop-in-a-loop might be able to be sped up, but it seems pretty clean so far
- Finally I return the last character in each stack for the result.
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.
- I copied the
part_one
method and renamed it - Then I removed the contents of the
instructions
loop, and started by getting the from stack as a mutable varaible. - I needed the mutable variable for the
split_off
method to get the number of crates to move in a solid … uhh… chunk? - The I append that chunk, on the ‘to’ stack.
- As in the
part_one
method, we return a string of the ‘top’ character of each of the stacks.
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);
}