-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
33 changed files
with
6,303 additions
and
5,807 deletions.
There are no files selected for viewing
Binary file not shown.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; |
Oops, something went wrong.