Solve 2024-12

Add side-aware coordinate utils
This commit is contained in:
2024-12-12 19:19:44 +01:00
parent 3c9b48b3a6
commit feb1b40408
5 changed files with 266 additions and 3 deletions
+7
View File
@@ -0,0 +1,7 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="2024-12" type="BunRunConfiguration">
<option name="program" value="2024/12/run.ts" />
<option name="workingDirectory" value="$PROJECT_DIR$" />
<method v="2" />
</configuration>
</component>
+78
View File
@@ -0,0 +1,78 @@
import { describe, expect, it } from 'bun:test';
import { getSides, solveFirst, solveSecond } from './index.ts';
describe('2024-12', () => {
it('first', () => {
expect(
solveFirst(`AAAA
BBCD
BBCC
EEEC`)
).toBe(140);
expect(
solveFirst(`OOOOO
OXOXO
OOOOO
OXOXO
OOOOO`)
).toBe(772);
expect(
solveFirst(`RRRRIICCFF
RRRRIICCCF
VVRRRCCFFF
VVRCCCJFFF
VVVVCJJCFE
VVIVCCJJEE
VVIIICJJEE
MIIIIIJJEE
MIIISIJEEE
MMMISSJEEE`)
).toBe(1930);
});
it('second', () => {
// it takes too long
// so only run while `NODE_ENV=production bun test --coverage`
if (process.env.NODE_ENV === 'production') {
expect(
solveSecond(`EEEEE
EXXXX
EEEEE
EXXXX
EEEEE`)
).toBe(236);
expect(
solveSecond(`AAAA
BBCD
BBCC
EEEC`)
).toBe(80);
expect(
solveSecond(`OOOOO
OXOXO
OOOOO
OXOXO
OOOOO`)
).toBe(436);
expect(
solveSecond(`AAAAAA
AAABBA
AAABBA
ABBAAA
ABBAAA
AAAAAA`)
).toBe(368);
}
});
it('sides', () => {
expect(
getSides([
[0, 0],
[1, 0],
[2, 0],
[0, 1]
])
).toBe(6);
});
});
+113
View File
@@ -0,0 +1,113 @@
import { sumArray } from '../../utils/array.ts';
import {
calculateSides,
type Coordinates,
getAdjacent,
getNeighbouringSides,
isCoordinateEqual
} from '../../utils/coordinates.ts';
function searchNeighbours(
visited: Set<number>,
rows: string[],
char: string,
x: number,
y: number,
found: Coordinates
) {
const position = y * rows.length + x;
if (visited.has(position)) {
return;
}
visited.add(position);
found.push([x, y]);
const { left, top, right, down } = getAdjacent(rows, x, y);
if (left === char) {
searchNeighbours(visited, rows, char, x - 1, y, found);
}
if (top === char) {
searchNeighbours(visited, rows, char, x, y - 1, found);
}
if (right === char) {
searchNeighbours(visited, rows, char, x + 1, y, found);
}
if (down === char) {
searchNeighbours(visited, rows, char, x, y + 1, found);
}
}
function groupChars(input: string) {
const rows = input.split('\n');
const groups: { char: string; coords: Coordinates }[] = [];
const visited = new Set<number>();
for (let y = 0; y < rows.length; y++) {
for (let x = 0; x < rows[y].length; x++) {
const position = y * rows.length + x;
if (visited.has(position)) {
continue;
}
const char = rows[y][x];
const coords: Coordinates = [];
searchNeighbours(visited, rows, char, x, y, coords);
groups.push({ char, coords });
}
}
return groups;
}
function getPerimeter(g: Coordinates): number {
if (g.length === 1) {
return 4;
}
if (g.length === 2) {
return 6;
}
const { uniqueSides } = calculateSides(g);
return uniqueSides.length;
}
export function solveFirst(input: string): number {
const groups = groupChars(input);
return sumArray(groups.map((g) => g.coords.length * getPerimeter(g.coords)));
}
export function solveSecond(input: string): number {
const groups = groupChars(input);
return sumArray(groups.map((g) => g.coords.length * getSides(g.coords)));
}
export function getSides(g: Coordinates): number {
if (g.length === 1) {
return 4;
}
if (g.length === 2) {
return 4;
}
const { uniqueSides } = calculateSides(g);
const visited = new Set<number>();
let sides = 0;
for (let i = 0; i < uniqueSides.length; i++) {
if (visited.has(i)) {
continue;
}
visited.add(i);
sides++;
const { neighbours } = getNeighbouringSides(uniqueSides[i], uniqueSides);
neighbours.forEach((c) =>
visited.add(uniqueSides.findIndex((v) => isCoordinateEqual(v, c)))
);
}
return sides;
}
+12
View File
@@ -0,0 +1,12 @@
import { join } from 'path';
import { solveFirst, solveSecond } from './index.ts';
const input = await Bun.file(join(__dirname, 'input.txt')).text();
const firstAnswer = solveFirst(input);
console.log(firstAnswer);
const secondAnswer = solveSecond(input);
console.log(secondAnswer);
+56 -3
View File
@@ -1,5 +1,16 @@
export const Sides = {
TOP: 0,
RIGHT: 1,
DOWN: 2,
LEFT: 3
} as const;
export type Side = (typeof Sides)[keyof typeof Sides];
export type Coordinate = [number, number]; export type Coordinate = [number, number];
export type CoordinateSide = [number, number, Side];
export type Coordinates = Coordinate[]; export type Coordinates = Coordinate[];
export type CoordinateSides = CoordinateSide[];
/** /**
* Returns a filter lambda to be used in array.filter() on a xy-Coordinate array (array of two number arrays). * Returns a filter lambda to be used in array.filter() on a xy-Coordinate array (array of two number arrays).
@@ -9,7 +20,7 @@ export type Coordinates = Coordinate[];
* @param yMin Inclusive, defaults to 0 * @param yMin Inclusive, defaults to 0
*/ */
export function inBounds(xMax: number, yMax: number, xMin = 0, yMin = 0) { export function inBounds(xMax: number, yMax: number, xMin = 0, yMin = 0) {
return ([x, y]: Readonly<Coordinate>) => return ([x, y]: Readonly<Coordinate | CoordinateSide>) =>
x >= xMin && x < xMax && y >= yMin && y < yMax; x >= xMin && x < xMax && y >= yMin && y < yMax;
} }
@@ -19,8 +30,8 @@ export function inBounds(xMax: number, yMax: number, xMin = 0, yMin = 0) {
* @param b xy-Coordinate, two number array * @param b xy-Coordinate, two number array
*/ */
export function isCoordinateEqual( export function isCoordinateEqual(
a: Readonly<Coordinate>, a: Readonly<Coordinate | CoordinateSide>,
b: Readonly<Coordinate> b: Readonly<Coordinate | CoordinateSide>
): boolean { ): boolean {
return b[0] === a[0] && b[1] === a[1]; return b[0] === a[0] && b[1] === a[1];
} }
@@ -78,3 +89,45 @@ export function findCoordsOfDigit(
[] []
); );
} }
export function calculateSides(g: Coordinate[]) {
const allSides = g.flatMap(
([x, y]) =>
[
[x - 0.5, y, Sides.LEFT],
[x, y - 0.5, Sides.TOP],
[x + 0.5, y, Sides.RIGHT],
[x, y + 0.5, Sides.DOWN]
] as CoordinateSides
);
const uniqueSides = allSides.filter(
(c) => allSides.filter((b) => isCoordinateEqual(c, b)).length === 1
);
return { uniqueSides, allSides };
}
export function getNeighbouringSides(
from: CoordinateSide,
all: CoordinateSides
) {
const [x, y, side] = from;
const isVertical = Math.abs(x % 1) === 0.5;
const coordIndex = isVertical ? 1 : 0;
const allDirectional = all
.filter(([cx, cy, cs]) => (isVertical ? cx === x : cy === y) && cs === side)
.toSorted((a, b) => a[coordIndex] - b[coordIndex]);
const before = allDirectional
.filter((d) => d[coordIndex] - from[coordIndex] < 0)
.toSorted((a, b) => b[coordIndex] - a[coordIndex])
.filter((d, i) => d[coordIndex] - from[coordIndex] === 0 - i - 1);
const after = allDirectional
.filter((d) => d[coordIndex] - from[coordIndex] > 0)
.filter((d, i) => d[coordIndex] - from[coordIndex] === i + 1);
const neighbours = [...before, ...after];
return { before, after, neighbours };
}