Skip to content

Commit 1c1036e

Browse files
committed
AoC2025: day 9 part 2
1 parent e14fd62 commit 1c1036e

2 files changed

Lines changed: 174 additions & 6 deletions

File tree

2024-2025/src/2025/day09/day09.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ describe('2025 Day 9', () => {
88
});
99

1010
test('Part 2', async () => {
11-
// expect(await part2('testInput1')).toEqual(31);
12-
// expect(await part2('input')).toEqual(29379307);
11+
expect(await part2('testInput1')).toEqual(24);
12+
expect(await part2('input')).toEqual(1603439684);
1313
});
1414

1515
test('findArea', async () => {
@@ -18,4 +18,4 @@ describe('2025 Day 9', () => {
1818
expect(findArea(new Coord(7, 3), new Coord(2, 3))).toEqual(6);
1919
expect(findArea(new Coord(7, 1), new Coord(11, 7))).toEqual(35);
2020
})
21-
});
21+
});

2024-2025/src/2025/day09/day09.ts

Lines changed: 171 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import path from "node:path";
22
import {readInputLineByLine} from "@utils/io";
33
import {Coord} from "@utils/grid";
4-
import {Coord3d} from "@utils/grid3d";
54

65
export async function part1(inputFile: string) {
76
return await day9(inputFile, findAreaOfLargestRectangle);
87
}
98

109
export async function part2(inputFile: string) {
11-
return await day9(inputFile);
10+
return await day9(inputFile, findAreaOfLargestRectanglePart2);
1211
}
1312

1413
async function day9(inputFile: string, calcFn?: (coords: Coord[]) => number) {
@@ -39,8 +38,177 @@ function findAreaOfLargestRectangle(coords: Coord[]): number {
3938
return maxArea;
4039
}
4140

41+
function findAreaOfLargestRectanglePart2(coords: Coord[]): number {
42+
const edges = buildEdges(coords);
43+
const {xs, ys, xIndex, yIndex} = buildCompressedAxes(coords);
44+
const prefix = buildInsidePrefix(edges, xs, ys);
45+
46+
let maxArea = 0;
47+
48+
for (let i = 0; i < coords.length; i++) {
49+
const a = coords[i];
50+
for (let j = i + 1; j < coords.length; j++) {
51+
const b = coords[j];
52+
if (a.equals(b)) continue;
53+
54+
const loX = Math.min(a.x, b.x);
55+
const hiX = Math.max(a.x, b.x);
56+
const loY = Math.min(a.y, b.y);
57+
const hiY = Math.max(a.y, b.y);
58+
59+
const area = (hiX - loX + 1) * (hiY - loY + 1);
60+
if (area <= maxArea) continue;
61+
62+
const x0 = xIndex.get(loX);
63+
const x1 = xIndex.get(hiX + 1);
64+
const y0 = yIndex.get(loY);
65+
const y1 = yIndex.get(hiY + 1);
66+
if (x0 === undefined || x1 === undefined || y0 === undefined || y1 === undefined) {
67+
continue;
68+
}
69+
70+
const insideSum =
71+
prefix[y1][x1] -
72+
prefix[y0][x1] -
73+
prefix[y1][x0] +
74+
prefix[y0][x0];
75+
76+
if (insideSum === area) {
77+
maxArea = area;
78+
}
79+
}
80+
}
81+
82+
return maxArea;
83+
}
84+
4285
export function findArea(a: Coord, b: Coord): number {
4386
const sideA = Math.abs(a.x - b.x) + 1;
4487
const sideB = Math.abs(a.y - b.y) + 1;
4588
return sideA * sideB;
46-
}
89+
}
90+
91+
type VerticalEdge = { x: number; loY: number; hiY: number };
92+
type HorizontalEdge = { y: number; loX: number; hiX: number };
93+
type Edges = { vertical: VerticalEdge[]; horizontal: HorizontalEdge[] };
94+
95+
function buildEdges(coords: Coord[]): Edges {
96+
const vertical: VerticalEdge[] = [];
97+
const horizontal: HorizontalEdge[] = [];
98+
99+
for (let i = 0; i < coords.length; i++) {
100+
const a = coords[i];
101+
const b = coords[(i + 1) % coords.length];
102+
103+
if (a.x === b.x) {
104+
const loY = Math.min(a.y, b.y);
105+
const hiY = Math.max(a.y, b.y);
106+
vertical.push({x: a.x, loY, hiY});
107+
} else {
108+
const loX = Math.min(a.x, b.x);
109+
const hiX = Math.max(a.x, b.x);
110+
horizontal.push({y: a.y, loX, hiX});
111+
}
112+
}
113+
114+
return {vertical, horizontal};
115+
}
116+
117+
function buildCompressedAxes(coords: Coord[]) {
118+
const xsSet = new Set<number>();
119+
const ysSet = new Set<number>();
120+
121+
for (const c of coords) {
122+
xsSet.add(c.x);
123+
xsSet.add(c.x + 1);
124+
ysSet.add(c.y);
125+
ysSet.add(c.y + 1);
126+
}
127+
128+
const xs = Array.from(xsSet).sort((a, b) => a - b);
129+
const ys = Array.from(ysSet).sort((a, b) => a - b);
130+
131+
const xIndex = new Map<number, number>();
132+
const yIndex = new Map<number, number>();
133+
134+
xs.forEach((x, i) => xIndex.set(x, i));
135+
ys.forEach((y, i) => yIndex.set(y, i));
136+
137+
return {xs, ys, xIndex, yIndex};
138+
}
139+
140+
function buildInsidePrefix(edges: Edges, xs: number[], ys: number[]) {
141+
const rowCount = ys.length - 1;
142+
const colCount = xs.length - 1;
143+
const dx = new Array<number>(colCount);
144+
for (let i = 0; i < colCount; i++) {
145+
dx[i] = xs[i + 1] - xs[i];
146+
}
147+
const xIndex = new Map<number, number>();
148+
xs.forEach((x, i) => xIndex.set(x, i));
149+
150+
const prefix: Float64Array[] = [];
151+
prefix.push(new Float64Array(colCount + 1));
152+
153+
for (let row = 0; row < rowCount; row++) {
154+
const y = ys[row];
155+
const height = ys[row + 1] - y;
156+
157+
const crossings: number[] = [];
158+
for (const edge of edges.vertical) {
159+
if (y >= edge.loY && y < edge.hiY) {
160+
crossings.push(edge.x);
161+
}
162+
}
163+
crossings.sort((a, b) => a - b);
164+
165+
const intervals: Array<[number, number]> = [];
166+
for (let i = 0; i + 1 < crossings.length; i += 2) {
167+
const start = crossings[i];
168+
const endInclusive = crossings[i + 1];
169+
intervals.push([start, endInclusive + 1]);
170+
}
171+
172+
for (const edge of edges.horizontal) {
173+
if (edge.y === y) {
174+
intervals.push([edge.loX, edge.hiX + 1]);
175+
}
176+
}
177+
178+
intervals.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
179+
180+
const merged: Array<[number, number]> = [];
181+
for (const [start, end] of intervals) {
182+
if (merged.length === 0) {
183+
merged.push([start, end]);
184+
continue;
185+
}
186+
const last = merged[merged.length - 1];
187+
if (start <= last[1]) {
188+
if (end > last[1]) last[1] = end;
189+
} else {
190+
merged.push([start, end]);
191+
}
192+
}
193+
194+
const rowWeights = new Float64Array(colCount);
195+
for (const [start, end] of merged) {
196+
const startIndex = xIndex.get(start);
197+
const endIndex = xIndex.get(end);
198+
if (startIndex === -1 || endIndex === -1) continue;
199+
if (startIndex === undefined || endIndex === undefined) continue;
200+
for (let x = startIndex; x < endIndex; x++) {
201+
rowWeights[x] = dx[x] * height;
202+
}
203+
}
204+
205+
const prev = prefix[row];
206+
const current = new Float64Array(colCount + 1);
207+
for (let x = 0; x < colCount; x++) {
208+
current[x + 1] = prev[x + 1] + current[x] - prev[x] + rowWeights[x];
209+
}
210+
prefix.push(current);
211+
}
212+
213+
return prefix;
214+
}

0 commit comments

Comments
 (0)