Advent of Code 2022, Day five - Part One
This image is generated using Dall-EPrompt: Generate an image of elves unloading a ship using a yellow cargo crane in a minimalistic flat style
Preface
Now that we have the following data structure from our previous post, we can start thinking about how we can execute the commands to fill up the stacks.
The data structures our sanitizer provides, are the stacks and the instructions.
1
2
3
4
5
[
Stack('N', 'Z'), // Stack 1
Stack('D', 'C', 'M'), // Stack 2
Stack('P') // Stack 3
]
1
2
3
4
5
6
[
Triple(1, 2, 1),
Triple(3, 1, 3),
Triple(2, 2, 1),
Triple(1, 1, 2)
]
Design
Now that we have all of our stacks, we need to execute the instructions one by one. We know that the order of the instruction is important, because that tells us how many items to move and from which to which stack to move that item. So, if we look at the assignment again, we see that an instruction looks like this.
move 1 from 2 to 1
This means that the first item in the triple is the number of stacks to move, the second item is the stack where we take the item from. And the last item is the stack where we need to store the item. So if we put it in code, we get something like this.
1
2
3
val noItemsToMove = Triple.first
val stackToRetrieveItemIndex = Triple.second - 1
val stackToStoreItemIndex = Triple.third - 1
In the above code, we subtract one from the value because the instruction gives us a 1-based stack number. But our index is 0-based.
Now that our data is correct and has a meaning, we can start the think about the way we need to execute our logic.
flowchart LR
subgraph stacksinput["Input stacks"]
stackinput1["<b>Stack 1</b>\n\nN\nZ"]
stackinput2["<b>Stack 2</b>\n\nD\nC\nM"]
stackinput3["<b>Stack 3</b>\n\nP"]
end
raw["Instructions"] --> lines["For each instruction"]
stacksinput --> lines
lines --> l1["1, 2, 1"]
lines --> l2["3, 1, 3"]
lines --> l3["2, 2, 1"]
lines --> l4["1, 1, 2"]
l1 --"val stackToRetrieveItem"-->getStackStart1["getStackAtPosition(2)"]
l2 --"val stackToRetrieveItem"-->getStackStart2["getStackAtPosition(1)"]
l3 --"val stackToRetrieveItem"-->getStackStart3["getStackAtPosition(2)"]
l4 --"val stackToRetrieveItem"-->getStackStart4["getStackAtPosition(1)"]
l1 --"val stackToStoreItem"-->getStackStore1["getStackAtPosition(1)"]
l2 --"val stackToStoreItem"-->getStackStore2["getStackAtPosition(3)"]
l3 --"val stackToStoreItem"-->getStackStore3["getStackAtPosition(1)"]
l4 --"val stackToStoreItem"-->getStackStore4["getStackAtPosition(2)"]
getStackStart1 --> execute1["moveItem(1, stackToRetrieveItem, stackToStoreItem)"]
getStackStore1 --> execute1
getStackStart2 --> execute2["moveItem(3, stackToRetrieveItem, stackToStoreItem)"]
getStackStore2 --> execute2
getStackStart3 --> execute3["moveItem(2, stackToRetrieveItem, stackToStoreItem)"]
getStackStore3 --> execute3
getStackStart4 --> execute4["moveItem(1, stackToRetrieveItem, stackToStoreItem)"]
getStackStore4 --> execute4
subgraph stacks1["Stacks"]
stack11["<b>Stack 1</b>\n\nD\nN\nZ"]
stack12["<b>Stack 2</b>\n\nC\nM"]
stack13["<b>Stack 3</b>\n\nP"]
end
subgraph stacks2["Stacks"]
stack21["<b>Stack 1</b>\n\n"]
stack22["<b>Stack 2</b>\n\nC\nM"]
stack23["<b>Stack 3</b>\n\nZ\nN\nD\nP"]
end
subgraph stacks3["Stacks"]
stack31["<b>Stack 1</b>\n\nM\nC"]
stack32["<b>Stack 2</b>\n\n"]
stack33["<b>Stack 3</b>\n\nZ\nN\nD\nP"]
end
subgraph stacks4["Stacks"]
stack41["<b>Stack 1</b>\n\nC"]
stack42["<b>Stack 2</b>\n\n\nM"]
stack43["<b>Stack 3</b>\n\nZ\nN\nD\nP"]
end
execute1 --> stacks1
execute2 --> stacks2
execute3 --> stacks3
execute4 --> stacks4
Implementation
Business logic
Now we have an idea of our design, let’s start implementing it in our PartOne class.
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
26
27
28
29
30
31
32
33
34
35
36
class PartOne(
val sanitizer: Sanitizer
) {
fun getResult(): String {
val stacks = sanitizer.getStacks()
sanitizer.getInstructions()?.forEach {
val noItemsToMove = it.first
val stackToRetrieveItemIndex = it.second - 1
val stackToStoreItemIndex = it.third - 1
val stackToRetrieveItem = stacks.get(stackToRetrieveItemIndex)
val stackToStoreItem = stacks.get(stackToStoreItemIndex)
moveItem(noItemsToMove, stackToRetrieveItem, stackToStoreItem)
}
return stacks.joinToString { // Step 3
it.peek().toString()
}
}
/***
* Move a specific number of items from one stack to another and remove them from the
* stack where the items are moved from.
*
* @param noItemsToMove the number of items to move
* @param stackToRetrieveItem the stack where the items are moved from
* @param stackToStoreItem the stack where the items are moved to
*/
private fun moveItem(noItemsToMove: Int, stackToRetrieveItem: Stack<Char>, stackToStoreItem: Stack<Char>) {
for (i in 1..noItemsToMove) { // Step 1
val item = stackToRetrieveItem.pop() // Step 2
stackToStoreItem.push(item)
}
}
}
Inside the moveItem
method we have a for-loop, Step 1, which ranges from 1 to the noItemsToMove
. This is done because this way our for-loop is exlusive. Which means that the loop is only executed once, if the noItemsToMove
contains the value 1
. If it was inclusive it would be executed twice, once for the 0
and once for the 1
value in the loop.
At Step 2 we use the pop
function on the stackToRetrieve
variable because that gives us the first item in the stack and removes it from that stack (Oracle, 2023) whereas the peek()
method returns the first item on the stack without removing that item (Oracle, 2023).
And finally at Step 3 we concatenate all the first items from each stack together to form our result.
Test case
We have written our business logic, and for our test input we now that the expected output for the test data is CMZ
. So we can start to write our test case.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class PartOneTest {
@Test
fun testGetResult() {
// Arrange
val resource = {}::class.java.getResource("/input.txt")
val sanitizer = Sanitizer(resource)
val sut = PartOne(sanitizer)
val expectedResult = "CMZ"
// Act
val result = sut.getResult()
// Assert
assertEquals(expectedResult, result)
}
}
Categories
Related articles