Skip to content

Commit

Permalink
day 12!
Browse files Browse the repository at this point in the history
  • Loading branch information
CodingAP committed Dec 12, 2024
1 parent e203b92 commit 603542c
Show file tree
Hide file tree
Showing 33 changed files with 6,303 additions and 5,807 deletions.
Binary file modified aoc/aoc_data.db
Binary file not shown.
414 changes: 207 additions & 207 deletions index.html

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions puzzles/2024/day12/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Advent of Code 2024 - Day 12: [Garden Groups](https://adventofcode.com/2024/day/12)

## [Write Up](https://codingap.github.io/advent-of-code/writeups/2024/day12)

## Results

| | **Part 1** | **Part 2** |
| :--------------: | :--------: | :--------: |
| **Results** | 1396298 | 853588 |
| **Time (in ms)** | 112.93 | 122.18 |

%%%

Leaderboard Positions - **Part 1**: 2165, **Part 2**: 1224

[Video Replay](https://youtu.be/nNCtZ-6a7wU)

Hello all! In this puzzle, we are figuring out the price of different regions based on their area and perimeter. Because we are working with grids, each character is considered `1` unit of area, and any edge that is not touching the same region is `1` unit of perimeter. For example...

```
AAAA
BBCD
BBCC
EEEC
is parsed as
----
|AAAA|
----
--
|BB|
|BB|
--
-
|C|_
|CC|
-|C|
-
-
|D|
-
--
|EE|
--
```

There could also being multiple regions that have the same name. If they are not touching, then they are different regions. In part 1, we need to count all the regions' area and perimeter, multiply them, then add it all up. To do this, we can use a recursive flood fill that returns the area and perimeter counts as well as the squares in the regions. Then, when looping over the grid, we can keep track of all the regions and make sure they one count once. For part 2, we need to combine perimeter edges that are next to each other. Using the example above, A will now have a perimeter of `4` and not `10`. C will have a perimeter of `8` and not `10`. This requires us to check which perimeters are next to each other and only count it once. I chose to loop over all directions, from smallest to largest, and make perimeters 'valid' or not if they needed to be counted.
122 changes: 122 additions & 0 deletions puzzles/2024/day12/solution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* puzzles/2024/day12/solution.ts
*
* ~~ Garden Groups ~~
* this is my solution for this advent of code puzzle
*
* by alex prosser
* 12/11/2024
*/

interface Perimeter {
x: number;
y: number;
valid: boolean
};

const UP = '0', RIGHT = '1', DOWN = '2', LEFT = '3';

/**
* floods a specific region and finds all units that belong to it
* captures the area and perimeter cells
*/
const floodFill = (grid: string[], details: { area: number, perimeter: { [key: string]: Perimeter[] } }, x: number, y: number, area: Set<string>) => {
if (area.has(`${x},${y}`)) return;
area.add(`${x},${y}`);

const width = grid[0].length, height = grid.length;
const plant = grid[y][x];

details.area++;
if (y === 0 || grid[y - 1][x] !== plant) details.perimeter[UP].push({ x, y, valid: true });
if (y === height - 1 || grid[y + 1][x] !== plant) details.perimeter[DOWN].push({ x, y, valid: true });
if (x === 0 || grid[y][x - 1] !== plant) details.perimeter[RIGHT].push({ x, y, valid: true });
if (x === width - 1 || grid[y][x + 1] !== plant) details.perimeter[LEFT].push({ x, y, valid: true });

if (y !== 0 && grid[y - 1][x] === plant) floodFill(grid, details, x, y - 1, area);
if (y !== height - 1 && grid[y + 1][x] === plant) floodFill(grid, details, x, y + 1, area);
if (x !== 0 && grid[y][x - 1] === plant) floodFill(grid, details, x - 1, y, area);
if (x !== width - 1 && grid[y][x + 1] === plant) floodFill(grid, details, x + 1, y, area);
}

/**
* the code of part 1 of the puzzle
*/
const part1 = (input: string) => {
const grid = input.trim().split('\n');
const width = grid[0].length, height = grid.length;

let alreadyFlooded = new Set<string>();
let sum = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
// only count unique regions
if (!alreadyFlooded.has(`${x},${y}`)) {
let details: { area: number, perimeter: { [key: string]: Perimeter[] } } = { area: 0, perimeter: { [UP]: [], [DOWN]: [], [LEFT]: [], [RIGHT]: [] } };
let visited = new Set<string>();
floodFill(grid, details, x, y, visited);
alreadyFlooded = alreadyFlooded.union(visited);

// count perimeter as total units
sum += details.area * Object.values(details.perimeter).reduce((sum, array) => sum + array.length, 0);
}
}
}

return sum;
};

/**
* the code of part 2 of the puzzle
*/
const part2 = (input: string) => {
const grid = input.trim().split('\n');
const width = grid[0].length, height = grid.length;

/**
* remove extra perimeter based on axis
*/
const filterPerimeters = (array: Perimeter[], primary: 'x' | 'y', secondary: 'x' | 'y') => {
// sort from smallest to largest
array.sort((a, b) => a[primary] - b[primary]);

// try to remove any perimeters on the same line
for (let i = 0; i < array.length; i++) {
let check = array[i][primary];
while (true) {
check++;
const nextNode = array.find(node => node[primary] === check && node[secondary] === array[i][secondary]);

// end check if not on continuous line
if (nextNode !== undefined) nextNode.valid = false;
else break;
}
}
}

let alreadyFlooded = new Set<string>();
let sum = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
if (!alreadyFlooded.has(`${x},${y}`)) {
let details: { area: number, perimeter: { [key: string]: Perimeter[] } } = { area: 0, perimeter: { [UP]: [], [DOWN]: [], [LEFT]: [], [RIGHT]: [] } };
let visited = new Set<string>();
floodFill(grid, details, x, y, visited);
alreadyFlooded = alreadyFlooded.union(visited);

// go through and remove extra perimeter not needed
Object.keys(details.perimeter).forEach(direction => {
if (direction === UP || direction === DOWN) filterPerimeters(details.perimeter[direction], 'x', 'y');
if (direction === LEFT || direction === RIGHT) filterPerimeters(details.perimeter[direction], 'y', 'x');
});

// only count perimeter which are 'valid'
sum += details.area * Object.values(details.perimeter).reduce((sum, array) => sum + array.filter(perimeter => perimeter.valid).length, 0);
}
}
}

return sum;
};

export { part1, part2 };
Loading

0 comments on commit 603542c

Please sign in to comment.