Skip to content

Commit 46eeb50

Browse files
committed
AoC2025: day 12
1 parent 464aa88 commit 46eeb50

3 files changed

Lines changed: 171 additions & 0 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {part1} from "./day12";
2+
3+
describe('2025 Day 12', () => {
4+
test('Part 1', async () => {
5+
expect(await part1('testInput1')).toEqual(3);
6+
expect(await part1('input')).toEqual(408);
7+
});
8+
});

2024-2025/src/2025/day12/day12.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import path from "node:path";
2+
import {readInputLineByLine} from "@utils/io";
3+
4+
export async function part1(inputFile: string) {
5+
return await day12(inputFile, countFittableRegions);
6+
}
7+
8+
async function day12(inputFile: string, calcFn?: (lines: string[]) => number) {
9+
const inputPath = path.join(__dirname, inputFile);
10+
const lines = await readInputLineByLine(inputPath);
11+
12+
return calcFn?.(lines);
13+
}
14+
15+
type ShapeInfo = {
16+
area: number;
17+
imbalance: number;
18+
};
19+
20+
type Region = {
21+
width: number;
22+
height: number;
23+
counts: number[];
24+
};
25+
26+
function countFittableRegions(lines: string[]): number {
27+
const {shapes, regions} = parseInput(lines);
28+
const shapeCount = shapes.length;
29+
let total = 0;
30+
31+
for (const region of regions) {
32+
if (region.counts.length !== shapeCount) {
33+
continue;
34+
}
35+
const areaNeeded = region.counts.reduce((sum, count, idx) => sum + count * shapes[idx].area, 0);
36+
const areaAvailable = region.width * region.height;
37+
if (areaNeeded > areaAvailable) continue;
38+
39+
if (areaNeeded === areaAvailable) {
40+
const boardImbalance = (areaAvailable % 2 === 0) ? 0 : 1;
41+
if (!canMatchImbalance(region.counts, shapes, boardImbalance)) continue;
42+
}
43+
44+
total++;
45+
}
46+
47+
return total;
48+
}
49+
50+
function parseInput(lines: string[]) {
51+
const shapes: ShapeInfo[] = [];
52+
const regions: Region[] = [];
53+
54+
const shapeHeader = /^(\d+):$/;
55+
const regionHeader = /^(\d+)x(\d+):/;
56+
57+
let i = 0;
58+
while (i < lines.length) {
59+
const line = lines[i].trim();
60+
if (shapeHeader.test(line)) {
61+
i++;
62+
const grid: string[] = [];
63+
while (i < lines.length) {
64+
const next = lines[i].trim();
65+
if (shapeHeader.test(next) || regionHeader.test(next)) break;
66+
grid.push(next);
67+
i++;
68+
}
69+
shapes.push(analyzeShape(grid));
70+
continue;
71+
}
72+
if (regionHeader.test(line)) {
73+
const [dimPart, countsPart] = line.split(":");
74+
const [width, height] = dimPart.trim().split("x").map(value => Number.parseInt(value, 10));
75+
const counts = countsPart.trim().split(/\s+/).map(value => Number.parseInt(value, 10));
76+
regions.push({width, height, counts});
77+
}
78+
i++;
79+
}
80+
81+
return {shapes, regions};
82+
}
83+
84+
function analyzeShape(grid: string[]): ShapeInfo {
85+
let area = 0;
86+
let black = 0;
87+
let white = 0;
88+
for (let y = 0; y < grid.length; y++) {
89+
for (let x = 0; x < grid[y].length; x++) {
90+
if (grid[y][x] !== "#") continue;
91+
area++;
92+
if ((x + y) % 2 === 0) black++;
93+
else white++;
94+
}
95+
}
96+
return {area, imbalance: Math.abs(black - white)};
97+
}
98+
99+
function canMatchImbalance(counts: number[], shapes: ShapeInfo[], boardImbalance: number): boolean {
100+
let maxSum = 0;
101+
for (let i = 0; i < counts.length; i++) {
102+
maxSum += counts[i] * shapes[i].imbalance;
103+
}
104+
if (maxSum === 0) {
105+
return boardImbalance === 0;
106+
}
107+
const offset = maxSum;
108+
let possible = new Array<boolean>(2 * maxSum + 1).fill(false);
109+
possible[offset] = true;
110+
111+
for (let i = 0; i < counts.length; i++) {
112+
const count = counts[i];
113+
const d = shapes[i].imbalance;
114+
if (count === 0 || d === 0) continue;
115+
const next = new Array<boolean>(2 * maxSum + 1).fill(false);
116+
for (let sum = -count * d; sum <= count * d; sum += 2 * d) {
117+
const shifted = sum + offset;
118+
for (let idx = 0; idx < possible.length; idx++) {
119+
if (!possible[idx]) continue;
120+
const target = idx + sum;
121+
if (target >= 0 && target < next.length) {
122+
next[target] = true;
123+
}
124+
}
125+
}
126+
possible = next;
127+
}
128+
129+
return possible[offset + boardImbalance] ?? false;
130+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
0:
2+
###
3+
##.
4+
##.
5+
6+
1:
7+
###
8+
##.
9+
.##
10+
11+
2:
12+
.##
13+
###
14+
##.
15+
16+
3:
17+
##.
18+
###
19+
##.
20+
21+
4:
22+
###
23+
#..
24+
###
25+
26+
5:
27+
###
28+
.#.
29+
###
30+
31+
4x4: 0 0 0 0 2 0
32+
12x5: 1 0 1 0 2 2
33+
12x5: 1 0 1 0 3 2

0 commit comments

Comments
 (0)