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

Advent of Code 2022, Day four - Part One

Advent of Code 2022, Day four - Part One
This image is generated using Dall-E
  • Prompt: Generate an image of elves unloading a big ship of supplies in a minimalistic flat style
  • Preface

    Now that we have the following data structure from our previous post, we can start by thinking about the business logic to check how many clean up ranges are completely inside another clean up range.

    The data structure our sanitizer provides, looks like this.

    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
    
    [
        {           // Line 1
          { 2, 4 }, // First elve
          { 6, 8 }  // Second elve
        },
        {           // Line 2
          { 2, 3 }, // First elve
          { 4, 5 }  // Second elve
        },
        {           // Line 3
          { 5, 7 }, // First elve
          { 7, 9 }  // Second elve
        },
        {           // Line 4
          { 2, 8 }, // First elve
          { 3, 7 }  // Second elve
        },
        {           // Line 5
          { 6, 6 }, // First elve
          { 4, 6 }  // Second elve
        },
        {           // Line 6
          { 2, 6 }. // First elve
          { 4, 8 }  // Second elve
        }
    ]
    

    Design

    Now we have each range for each elve seperated we can start to think about how we want to achieve the check wheter or not one range is completely inside another range.

    We can do this by checking if one pair is completely inside another pair, in pseudo-code this would look something like this.

    1
    2
    3
    4
    5
    6
    
    fun isRangeInsideAnother(rangeA: Pair<Int, Int>, rangeB: Pair<Int, Int>): Boolean {
      val isStartInsideRangeA = rangeB.first >= rangeA.first && rangeB.first <= rangeA.second
      val isEndInsideRangeA = rangeB.second >= rangeA.first && rangeB.second <= rangeA.second
    
      return isStartInsideRangeA && isEndInsideRangeA
    }
    

    For this to work both ways, we need to call the above method twice in the order of isRangeInsideAnotherRange(rangeA, rangeB) and isRangeInsideAnotherRange(rangeB, rangeA). And if we then or the result, we get the expected behaviour.

    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
    
    class PartOne(
        private val sanitizer: Sanitizer
    ) {
        fun getResult(): Int {
            val rangesThatOverlapCompletely = sanitizer
                .getItems()
                ?.filter { // Step 1
                    isRangeInsideAnother(it.first, it.second) ||
                    isRangeInsideAnother(it.second, it.first)
                }
                ?.count()
    
            return rangesThatOverlapCompletely ?: -1
        }
    
        /***
         * Check if rangeB is completely inside rangeA
         *
         * @param rangeA the range that is used to check rangeB against
         * @param rangeB the range that is checked against rangeA
         */
        private fun isRangeInsideAnother(rangeA: Pair<Int, Int>, rangeB: Pair<Int, Int>): Boolean {
            val isStartInsideRangeA = rangeB.first >= rangeA.first && rangeB.first <= rangeA.second
            val isEndInsideRangeA = rangeB.second >= rangeA.first && rangeB.second <= rangeA.second
    
            return isStartInsideRangeA && isEndInsideRangeA
        }
    }
    

    At Step 1 we filter the input elements based on the overlapping ranges. We do this by validating that rangeB is inside rangeA or if rangeA is inside rangeB.

    Test case

    We have written our business logic, and for our test input we now that the expected number of overlapping ranges is 2. So we can start setting up 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 expectedNumberOfOverlappingPairs = 2
    
            // Act
            val result = sut.getResult()
    
            // Assert
            assertEquals(expectedNumberOfOverlappingPairs, result)
        }
    }
    

    Categories

    Related articles

    Advent of Code 2022, Day four - Sanitizer

    Turn the input into a list of assigned compartments for the elves to unload.

    Advent of Code 2022, Day four - Part Two

    Find the compartments that overlap completely between the different assignments.