Post

Advent of Code 2022, Day 5 - Part One

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)
    }
}
This post is licensed under CC BY 4.0 by the author.