Files
advent-of-code-2021-firebase/functions/src/day9.ts
2021-12-09 23:25:27 +01:00

122 lines
3.2 KiB
TypeScript

import Day from "./day";
import Vector2 from "./vector2";
import Utils from "./utils";
class Day9 implements Day {
part1(input: string[]): number | string {
const heightMap = HeightMap.parseInput(input);
const lowPoints = heightMap.findLowPoints();
return Utils.sum(lowPoints.map(lp => heightMap.height(lp.x, lp.y) + 1));
}
part2(input: string[]): number | string {
const heightMap = HeightMap.parseInput(input);
const basins = heightMap.findBasins();
basins.sort((a, b) => b.size - a.size);
return basins[0].size * basins[1].size * basins[2].size;
}
}
class HeightMap {
readonly heightMap: number[];
readonly width: number;
readonly maxY: number;
constructor(heightMap: number[], width: number) {
this.heightMap = heightMap;
this.width = width;
this.maxY = this.heightMap.length / width;
}
static parseInput(input: string[]): HeightMap {
const width = input[0].length;
const heightMap = [];
for (const line of input) {
if (line.length !== width) {
throw Error("Invalid input");
}
heightMap.push(...line.split("").map(s => parseInt(s)));
}
return new HeightMap(heightMap, width);
}
height(x: number, y: number): number {
if (x < 0 || x >= this.width || y < 0 || y >= this.maxY) return 9;
return this.heightMap[x + y * this.width];
}
findLowPoints(): Vector2[] {
const res = [];
for (let y = 0; y < this.maxY; y++) {
for (let x = 0; x < this.width; x++) {
if (this.isLowPoint(x, y)) {
res.push(new Vector2(x, y));
}
}
}
return res;
}
isLowPoint(x: number, y: number): boolean {
const height = this.height(x, y);
return height < this.height(x - 1, y) &&
height < this.height(x + 1, y) &&
height < this.height(x, y - 1) &&
height < this.height(x, y + 1);
}
findBasins(): Basin[] {
const lowPoints = this.findLowPoints();
// From every low point, we start to grow "upwards" to claim every point that is part of it's basin:
const basinIds = Array(this.width * this.maxY).fill(-1);
for (let i = 0; i < lowPoints.length; i++) {
const stack: Vector2[] = [lowPoints[i]];
while(stack.length > 0) {
const pos = stack.pop();
if (!pos) {
throw Error("Stack is empty?")
}
const posHeight = this.height(pos.x, pos.y);
basinIds[pos.x + pos.y * this.width] = i;
// We add all neighbors to the stack that are low enough, and haven't been assigned to a basin:
const neighbors = [pos.addX(1), pos.addX(-1), pos.addY(1), pos.addY(-1)];
for (const neighbor of neighbors) {
const neighborHeight = this.height(neighbor.x, neighbor.y);
if (neighborHeight < 9 && neighborHeight > posHeight && basinIds[neighbor.x + neighbor.y * this.width] === -1) {
stack.push(neighbor);
}
}
}
}
return lowPoints.map((lp, i) => new Basin(lp, basinIds.filter(b => b === i).length));
}
}
class Basin {
readonly lowPoint: Vector2;
readonly size: number;
constructor(lowPoint: Vector2, size: number) {
this.lowPoint = lowPoint;
this.size = size;
}
}
export default Day9;