Skip to content

Commit e2d2bec

Browse files
committed
18/2024
1 parent 9576ed2 commit e2d2bec

File tree

4 files changed

+273
-0
lines changed

4 files changed

+273
-0
lines changed

2024/18/example.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
5,4
2+
4,2
3+
4,5
4+
3,0
5+
2,1
6+
6,3
7+
2,4
8+
1,5
9+
0,6
10+
3,3
11+
2,6
12+
5,1
13+
1,2
14+
5,5
15+
2,5
16+
6,5
17+
1,4
18+
0,4
19+
6,4
20+
1,1
21+
6,1
22+
1,0
23+
0,5
24+
1,6
25+
2,0

2024/18/index.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { expect, describe, test } from "bun:test";
2+
import { part1, part2 } from ".";
3+
import { getInputs } from "../../utils/get-inputs";
4+
5+
const { exampleInput, puzzleInput } = await getInputs("2024/18");
6+
7+
describe("part 1", () => {
8+
test("example", () => {
9+
expect(part1(exampleInput, 6, 12)).toBe(22);
10+
});
11+
12+
test("puzzle", () => {
13+
expect(part1(puzzleInput, 70, 1024)).toBe(364);
14+
});
15+
});
16+
17+
describe("part 2", () => {
18+
test("example", () => {
19+
expect(part2(exampleInput, 6, 12)).toBe("6,1");
20+
});
21+
test("puzzle", () => {
22+
expect(part2(puzzleInput, 70, 1024)).toBe("52,28");
23+
});
24+
});

2024/18/index.ts

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import { timePart1, timePart2 } from "../../utils/time-part";
2+
3+
type Coordinate = { x: number; y: number };
4+
5+
const parseInput = (input: string) => {
6+
const coords = input.split("\n").map((coord) => {
7+
const [x, y] = coord.split(",").map(Number);
8+
9+
return { x, y } satisfies Coordinate;
10+
});
11+
12+
return coords;
13+
};
14+
15+
const createGrid = (size: number) =>
16+
[...Array(size).keys()].map(() => [...Array(size).keys()].map(() => "."));
17+
18+
type Grid = ReturnType<typeof createGrid>;
19+
20+
const populateGrid = (grid: Grid, obstacles: Coordinate[]) => {
21+
const newGrid = JSON.parse(JSON.stringify(grid)) as typeof grid;
22+
23+
for (let i = 0; i < obstacles.length; i++) {
24+
const coord = obstacles[i];
25+
newGrid[coord.y][coord.x] = "#";
26+
}
27+
28+
return newGrid;
29+
};
30+
31+
const printGrid = (grid: Grid) => {
32+
console.log(grid.map((row) => row.join("")).join("\n"));
33+
};
34+
35+
const DIRECTIONS = {
36+
UP: { x: 0, y: -1 },
37+
RIGHT: { x: 1, y: 0 },
38+
DOWN: { x: 0, y: 1 },
39+
LEFT: { x: -1, y: 0 },
40+
};
41+
42+
type Direction = keyof typeof DIRECTIONS;
43+
44+
const move = ({ x, y }: Coordinate, dirKey: Direction) => {
45+
const direction = DIRECTIONS[dirKey];
46+
47+
return { x: x + direction.x, y: y + direction.y };
48+
};
49+
50+
const coordToKey = ({ x, y }: Coordinate) => `${x},${y}`;
51+
52+
class BinaryMinHeap {
53+
private heap: Array<{
54+
position: Coordinate;
55+
count: number;
56+
visited: Coordinate[];
57+
}>;
58+
59+
constructor() {
60+
this.heap = [];
61+
}
62+
63+
getParentIndex(index: number) {
64+
return Math.floor((index - 1) / 2);
65+
}
66+
67+
getLeftChildIndex(index: number) {
68+
return 2 * index + 1;
69+
}
70+
71+
getRightChildIndex(index: number) {
72+
return 2 * index + 2;
73+
}
74+
75+
swap(index1: number, index2: number) {
76+
[this.heap[index1], this.heap[index2]] = [
77+
this.heap[index2],
78+
this.heap[index1],
79+
];
80+
}
81+
82+
// Insert a new element into the heap
83+
insert(element: (typeof this.heap)[number]) {
84+
this.heap.push(element);
85+
this.bubbleUp();
86+
}
87+
88+
bubbleUp() {
89+
let index = this.heap.length - 1;
90+
91+
while (
92+
index > 0 &&
93+
this.heap[index].count < this.heap[this.getParentIndex(index)].count
94+
) {
95+
this.swap(index, this.getParentIndex(index));
96+
index = this.getParentIndex(index);
97+
}
98+
}
99+
100+
// Remove and return the smallest element
101+
extractMin() {
102+
if (this.heap.length === 1) return this.heap.pop()!;
103+
104+
const min = this.heap[0];
105+
this.heap[0] = this.heap.pop()!;
106+
this.bubbleDown();
107+
return min;
108+
}
109+
110+
bubbleDown() {
111+
let index = 0;
112+
113+
while (this.getLeftChildIndex(index) < this.heap.length) {
114+
let smallerChildIndex = this.getLeftChildIndex(index);
115+
116+
if (
117+
this.getRightChildIndex(index) < this.heap.length &&
118+
this.heap[this.getRightChildIndex(index)].count <
119+
this.heap[this.getLeftChildIndex(index)].count
120+
) {
121+
smallerChildIndex = this.getRightChildIndex(index);
122+
}
123+
124+
if (this.heap[index].count <= this.heap[smallerChildIndex].count) {
125+
break;
126+
}
127+
128+
this.swap(index, smallerChildIndex);
129+
index = smallerChildIndex;
130+
}
131+
}
132+
133+
isEmpty() {
134+
return this.heap.length === 0;
135+
}
136+
}
137+
138+
const getLowestStepCount = ({
139+
grid,
140+
gridSize,
141+
}: {
142+
grid: Grid;
143+
gridSize: number;
144+
}) => {
145+
const start = { x: 0, y: 0 };
146+
const end = { x: gridSize, y: gridSize };
147+
const visited = new Map<string, number>();
148+
let lowestStepCount = Number.MAX_SAFE_INTEGER;
149+
150+
const queue = new BinaryMinHeap();
151+
queue.insert({ position: start, count: 0, visited: [start] });
152+
153+
while (!queue.isEmpty()) {
154+
const entry = queue.extractMin()!;
155+
const key = coordToKey(entry.position);
156+
157+
if (visited.has(key) && visited.get(key)! <= entry.count) {
158+
continue;
159+
}
160+
161+
visited.set(key, entry.count);
162+
163+
if (entry.position.x === end.x && entry.position.y === end.y) {
164+
if (entry.count < lowestStepCount) {
165+
lowestStepCount = entry.count;
166+
}
167+
168+
continue;
169+
}
170+
171+
for (const direction of Object.keys(DIRECTIONS) as Direction[]) {
172+
const newPosition = move(entry.position, direction);
173+
const nextCount = entry.count + 1;
174+
175+
if (
176+
newPosition.x >= 0 &&
177+
newPosition.x <= gridSize &&
178+
newPosition.y >= 0 &&
179+
newPosition.y <= gridSize &&
180+
grid[newPosition.y][newPosition.x] !== "#" &&
181+
nextCount < lowestStepCount
182+
) {
183+
queue.insert({
184+
position: newPosition,
185+
count: entry.count + 1,
186+
visited: [...entry.visited, newPosition],
187+
});
188+
}
189+
}
190+
}
191+
192+
return lowestStepCount;
193+
};
194+
195+
export const part1 = timePart1(
196+
(input: string, gridSize: number, byteCount: number) => {
197+
const coords = parseInput(input);
198+
const grid = populateGrid(
199+
createGrid(gridSize + 1),
200+
coords.slice(0, byteCount)
201+
);
202+
203+
return getLowestStepCount({ grid, gridSize });
204+
}
205+
);
206+
207+
export const part2 = timePart2(
208+
(input: string, gridSize: number, byteCount: number) => {
209+
const coords = parseInput(input);
210+
const baseGrid = createGrid(gridSize + 1);
211+
212+
// @todo: optimize
213+
for (let i = byteCount + 1; i < coords.length; i++) {
214+
const grid = populateGrid(baseGrid, coords.slice(0, i + 1));
215+
216+
const lowestStepCount = getLowestStepCount({ grid, gridSize });
217+
218+
if (lowestStepCount === Number.MAX_SAFE_INTEGER) {
219+
return coordToKey(coords[i]);
220+
}
221+
}
222+
}
223+
);

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
| Day | Part 1 | Part 2 |
88
| :----------------------------------------: | :----: | :----: |
9+
| [18](https://adventofcode.com/2024/day/18) |||
910
| [17](https://adventofcode.com/2024/day/17) |||
1011
| [16](https://adventofcode.com/2024/day/16) |||
1112
| [15](https://adventofcode.com/2024/day/15) |||

0 commit comments

Comments
 (0)