Bart Kessels
Bart Kessels
Passionate open source software engineer who loves to go backpacking

Advent of Code 2022, Day five - Part One

Advent of Code 2022, Day five - Part One
This image is generated using Dall-E
  • Prompt: 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

    Advent of Code 2022, Day five - Sanitizer

    Turn the input into a list of stacks and another list of operations for the crane.

    Advent of Code 2022, Day 5 - Part Two

    Execute the instructions to move the cargo from one stack to another in the same order.