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;