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

Advent of Code 2022, Day 4 - Part Two

Preface

In this part of the advent of code, we need to check if two ranges overlap at all. So we can take the code from our previous part and change a couple of lines.

Because the assignment is almost the same but with the only exception that we need to check how many ranges overlap at all compared to how many ranges overlap completely.

Design

Let’s take a look at our design from the previous post.

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
}

In this method we check if the start of rangeB and the end of rangeB is inside rangeA. The keyword here is and. If we change the operation by checking if the start of rangeB is inside rangeA or if the end of rangeB is inside rangeA we get all the ranges that overlap at some point. So it’ll look like this.

1
2
3
4
5
6
fun doesRangeOverlap(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 doesRangeOverlap(rangeA, rangeB) and doesRangeOverlap(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 PartTwo 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 PartTwo(
    private val sanitizer: Sanitizer
) {
    fun getResult(): Int {
        val rangesThatOverlap = sanitizer
            .getItems()
            ?.filter { // Step 1
                doesRangeOverlap(it.first, it.second) ||
                doesRangeOverlap(it.second, it.first)
            }
            ?.count()

        return rangesThatOverlap ?: -1
    }

    /***
     * Check if rangeB overlaps somewehere with rangeA
     *
     * @param rangeA the range that is used to check rangeB against
     * @param rangeB the range that is checked against rangeA
     */
    private fun doesRangeOverlap(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 overlaps with rangeA or if rangeA overlaps with rangeB.

Test case

We have written our business logic, and for our test input we now that the expected number of overlapping ranges is 4. 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 PartTwoTest {
    @Test
    fun testGetResult() {
        // Arrange
        val resource = PartTwoTest::class.java.getResource("/input.txt")
        val sanitizer = Sanitizer(resource)
        val sut = PartTwo(sanitizer)
        val expectedNumberOfOverlappingPairs = 4

        // Act
        val result = sut.getResult()

        // Assert
        assertEquals(expectedNumberOfOverlappingPairs, result)
    }
}
This post is licensed under CC BY 4.0 by the author.