Supply Stacks
Heavy data parsing in C.
Last updated
Heavy data parsing in C.
Last updated
The full description can be found in SupplyStacks.zip
.
You are going to write an algorithm that opens a file input.txt
and follows a series of instructions to switch supply crates between stacks. You must track the crates that are on the top of each stack.
The top of the file contains the current arrangement of the crates. Following that is the rearrangement procedure.
The Marines just need to know which crates will be on top of the stack. Submit your flag as the order of what's on the top of each stack wrapped in flag braces, i.e. flag{CMZ}
.
This is a difficult challenge. This is a complicated file to parse. The primary difference between this file and the challenges before this is that this challenge comes with initial data, meaning we can't simply start parsing instructions. We need to take time to collect the initial data before parsing the instructions. We'll quickly learn that doing this in C isn't all that fun.
Before we start, let's pick a data structure. We'll logically choose a stack as a good choice for this problem. There are two ways to implement stacks in C:
As a static array, use a pointer to track where the top of the stack is.
As a linked list, use a pointer to the tail node to indicate the top of the stack.
For this solution, I chose to use a static array. This is a less space-efficient solution. I challenge you to rework this solution to use a linked list instead.
We will complete this challenge in two parts. First, we will read and store the initial data in our stack. Then, we will read the instructions and update our stack accordingly.
We'll start with the same initializations as the previous challenges. We'll open the file, ensure it's opened, and initialize our read buffer.
We will use the same getline
loop to read the data. When we read the stack data, we are unsure which stacks have data on each line, so it's easier to parse it knowing the entire line.
The first thing we must do is determine the number of stacks. At the same time, we will track the last line of stack data so we know how much to parse when we initialize the stacks.
Based on the input, the line containing the stack numbers begins with a space. We will read until our first character is a space. Once we have this line, we will go to the end of the line and read off the last number. This will be the number of stacks.
We define des_line
as the number of lines containing stack data. We do this so that when we return to read the lines of data, we know how many lines to read.
Since we aren't sure of the size of the number, we have to simply read from the last number to the space. This isn't the best solution, but it works. A plausible second solution would be to use strtok()
to get each line number. Here is that solution:
Let's talk this solution. This solution uses strtok()
to delimit the stack numbers. However, when we reach the end of the line, we'll notice that we hit a 0xd
. This is a carriage return. This is a special character used to delimit new lines. For most modern processing, newlines can be represented as 0xd0xa
or . This is because the original ASCII standard used 0xd
to represent newlines. This is why we have to check for 0xd
in our strtok()
loop.
This is not an intuitive solution. I originally tried checking token[0] ==
and it took me a minute to debug why this solution didn't work.
Now that we have the number of stacks, we need to re-read the start of the file until we reach this line. First, we need to reset the file pointer to the start of the file.
Note: This can also be doing by simply reopening the file.
This is not a wrong solution; fseek()
is more efficient.
Next, we should allocate the storage. We need num_stacks
stacks. Since we chose to use a static array, we will define a size for each stack. I chose 100
.
At the same time, we also must track the number of crates on each stack. We will use a second array to track this.
Once we do this, we need to read the data. We defined des_line
earlier, which we tracked as the number of lines containing stack data. We will read lines until we reach this number of lines
For each line, we need to parse the data. We can take advantage of the data format to figure out what column we're in.
Based on the character number we're at, we will be on stack 1 + char_num / 4
. This is because each stack item is four characters long (two brackets, one letter, and one space).
We can check each character to see if it's alphanumeric. If it is, char_num / 4
will tell us what column we're in. Note: I removed the 1 +
because our array uses zero-based indexing, while the stack numbers are one-based.
We can simply increment the size of the stack as we increment using the post-increment operator.
Let's make this happen.
We now have the initial data stored. However, it's not quite right. We read the stacks from top to bottom, meaning that the top of the stacks are at the bottom. We need to reverse each stack to fix this.
We're almost done! All we need to do is move the FILE*
pointer to the start of the instructions. We know this is two lines away (the stack numbers line plus the newline).
Now, our data is initialized. We can move on to parsing the instructions.
Thankfully, this is the easy part. We can easily read the rest of the lines and get the data we need.
We need three unsigned integers: the number of moving items, the source stack, and the destination stack. We can use strtok()
to get these values. We can ignore the strings in between but can't forget to read them.
How do we move the items between stacks? We need to move them in reverse order. We can use a for
loop to count from 0
to moving
and move them from the old stack to the new stack. At the same time, we can post-decrement size[destination]
and size[origin]
.
We can do this in a nice ugly one-liner.
Now that the data has been processed, we can print the flag. The flag is simply the top of each stack. We can use a nice for
loop to handle this:
Don't forget to free the memory! We allocated memory for the stacks, the sizes, and the line buffer. We need to free all of these.
Running this prints our flag.
A note on this solution:
You'll notice that if you attempt to print the stacks to watch the program run, it will not work as expected. This is because I chose to use a static array and a pointer to watch the top of the stack. In doing so, I neglect to clear the data that I no longer need. This means that the data is still there, but it's not being tracked. This is why the flag is correct, but the output is not.
A linked list solution mitigates this problem.
The full solution is below: