Home Advent of Code 2022, Day 4 - Part One
Post
Cancel

Advent of Code 2022, Day 4 - Part One

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