diff --git a/README.md b/README.md index aa28fdf..0644b36 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,16 @@ The algorithm starts at the trail ends (the `9`'s) and searches all options from Since the order of the pebbles does not actually matter, we can simply blink from one line of pebbles to the next and count only cardinalities of each number in the current line. +### Day 12: Garden Groups + +:mag_right: Puzzle: , :white_check_mark: Solver: [`GardenGroups.cpp`](src/GardenGroups.cpp) + +The algorithm uses two stacks to flood-fill each garden region; one stack for all plots inside the region, and one stack to collect all other plots encountered during the flood-fill. Once a region has been traversed, it just pops the next plot from the latter stack. This gives us naturally the area of a region. + +The perimeter of a region is the sum of neighbors for all plots encountered during the traversal, that are not in the same region, also counting neighbors outside the defined area. The number of sides of a region is equal to the number of corners of a region, which can also be counted per plot while doing the region traversal. A corner occurs for each pair of adjacent cardinal neighbors if they are either both outside of the current region, or if they are both inside the region and the diagonal neighbor in between them is not. + +Adding up the products of area and perimeter, or area and corner count, per region gives us the solution. + ## Thanks * [Alexander Brouwer](https://github.com/Bromvlieg) for getting the project set up with CMake. diff --git a/src/GardenGroups.cpp b/src/GardenGroups.cpp index e2602d3..b3d3cdd 100644 --- a/src/GardenGroups.cpp +++ b/src/GardenGroups.cpp @@ -53,6 +53,7 @@ void GardenGroups::traverseRegion(Grid& isVisited, std::stack& oth const char plantType{ getCharAt(start) }; long long int area{ 0 }; long long int perimeter{ 0 }; + long long int nCorners{ 0 }; std::stack regionStack{}; regionStack.push(start); @@ -65,34 +66,54 @@ void GardenGroups::traverseRegion(Grid& isVisited, std::stack& oth isVisited.cell(position) = true; area++; + Point2 previousNeighbor{ position + Point2::cardinalDirections.back() }; + bool isPreviousNeighborSameRegion{ + isInBounds(previousNeighbor) && getCharAt(previousNeighbor) == plantType + }; for (const Point2& direction : Point2::cardinalDirections) { - const Point2 next{ position + direction }; - if (isInBounds(next)) + const Point2 neighbor{ position + direction }; + const bool isNeighborSameRegion{ isInBounds(neighbor) && getCharAt(neighbor) == plantType }; + + if (isNeighborSameRegion) { - if (getCharAt(next) == plantType) + if (!isVisited.cell(neighbor)) { - if (!isVisited.cell(next)) - { - regionStack.push(next); - } + regionStack.push(neighbor); } - else + + // Increases the corner count if two adjacent neighbors are both inside this region, but the + // diagonal neighbor in between them is outside this region. + if (isPreviousNeighborSameRegion) { - perimeter++; - if (!isVisited.cell(next)) + const Point2 diagonalNeighbor{ previousNeighbor + direction }; + if (!isInBounds(diagonalNeighbor) || getCharAt(diagonalNeighbor) != plantType) { - otherRegionsStack.push(next); + nCorners++; } } } else { + if (isInBounds(neighbor) && !isVisited.cell(neighbor)) + { + otherRegionsStack.push(neighbor); + } + perimeter++; + + // Increases the corner count if two adjacent neighbors are both outside this region. + if (!isPreviousNeighborSameRegion) + { + nCorners++; + } } + previousNeighbor = neighbor; + isPreviousNeighborSameRegion = isNeighborSameRegion; } } } part1 += area * perimeter; + part2 += area * nCorners; } diff --git a/tests/src/TestCases.cpp b/tests/src/TestCases.cpp index f91f058..e2afe14 100644 --- a/tests/src/TestCases.cpp +++ b/tests/src/TestCases.cpp @@ -182,19 +182,27 @@ TEST_CASE("[GardenGroupsTests]") TestContext test; SECTION("FullData") { - test.run(std::make_unique(), 1477762, 0, test.getInputPaths()); + test.run(std::make_unique(), 1477762, 923480, test.getInputPaths()); } SECTION("ExampleData") { - test.run(std::make_unique(), 1930, 0, test.getExampleInputPaths()); + test.run(std::make_unique(), 1930, 1206, test.getExampleInputPaths()); } SECTION("ExampleData2") { - test.run(std::make_unique(2), 140, 0, test.getExampleInputPaths()); + test.run(std::make_unique(2), 140, 80, test.getExampleInputPaths()); } SECTION("ExampleData3") { - test.run(std::make_unique(3), 772, 0, test.getExampleInputPaths()); + test.run(std::make_unique(3), 772, 436, test.getExampleInputPaths()); + } + SECTION("ExampleData4") + { + test.runPart2(std::make_unique(4), 236, test.getExampleInputPaths()); + } + SECTION("ExampleData5") + { + test.runPart2(std::make_unique(5), 368, test.getExampleInputPaths()); } }