122 lines
3.2 KiB
TypeScript
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; |