diff --git a/2024/21/2024-21.run.xml b/2024/21/2024-21.run.xml new file mode 100644 index 0000000..3adaa76 --- /dev/null +++ b/2024/21/2024-21.run.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/2024/21/index.test.ts b/2024/21/index.test.ts new file mode 100644 index 0000000..5086329 --- /dev/null +++ b/2024/21/index.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from 'bun:test'; +import { solveFirst } from './index.ts'; + +describe('2024-21', () => { + it('first', () => { + const testInput = `029A +980A +179A +456A +379A`; + expect(solveFirst(testInput)).toBe(126384); + }); +}); diff --git a/2024/21/index.ts b/2024/21/index.ts new file mode 100644 index 0000000..1a712ea --- /dev/null +++ b/2024/21/index.ts @@ -0,0 +1,120 @@ +import { sumBy } from '../../utils/array.ts'; +import { + type Coordinate, + type Coordinates, + findCoordsOfDigit +} from '../../utils/coordinates.ts'; + +const DIRS = [ + // up + [0, -1], + // right + [1, 0], + // down + [0, 1], + // left + [-1, 0] +] as Coordinates; + +const DIRS_LIT = ['^', '>', 'v', '<']; +const numPad = ['789', '456', '123', '#0A']; +const dirPad = ['#^A', '']; + +const findShortestPaths = (pad: string[], startVal: string, endVal: string) => { + const start = findCoordsOfDigit(pad, startVal === '^' ? /\^/g : startVal)[0]; + const stack: { + p: Coordinate; + path: string[]; + cost: number; + dirId?: number; + }[] = [{ p: start, path: [], cost: 0 }]; + const seen: Map = new Map(); + const paths: string[][] = []; + let minCost = Infinity; + + while (stack.length) { + const { + cost, + dirId, + p: [x, y], + path + } = stack.shift()!; + if (dirId !== undefined) { + path.push(DIRS_LIT[dirId]); + } + if (pad[y]?.[x] === endVal) { + if (cost < minCost) { + paths.length = 0; + minCost = cost; + } + if (cost === minCost) paths.push(path); + continue; + } + + const k = `${x}_${y}`; + if ((seen.get(k) || Infinity) < cost) { + continue; + } + seen.set(k, cost); + if (cost > minCost) { + continue; + } + + DIRS.forEach(([dx, dy], dirId) => { + const [px, py] = [x + dx, y + dy]; + if (pad[py]?.[px] === '#') { + return; + } + stack.push({ + path: [...path], + p: [px, py], + dirId, + cost: cost + 1 + }); + }); + } + + return paths.map((p) => p.join('') + 'A'); +}; + +const execute = ( + pad: string[], + code: string, + depth: number, + memo: Map = new Map() +) => { + const k = `${code}_${depth}`; + const saved = memo.get(k); + if (saved !== undefined) return saved; + + let curPos = 'A'; + let length = 0; + + for (const nextPos of code.split('')) { + const paths = findShortestPaths(pad, curPos, nextPos); + if (depth === 0) { + length += paths[0].length; + } else { + length += Math.min( + ...paths.map((path) => execute(dirPad, path, depth - 1, memo)) + ); + } + curPos = nextPos; + } + + memo.set(k, length); + return length; +}; +export function solveFirst(input: string): number { + return sumBy( + input.split('\n'), + (code) => parseInt(code) * execute(numPad, code, 2) + ); +} + +export function solveSecond(input: string): number { + return sumBy( + input.split('\n'), + (code) => parseInt(code) * execute(numPad, code, 25) + ); +} diff --git a/2024/21/run.ts b/2024/21/run.ts new file mode 100644 index 0000000..88783cd --- /dev/null +++ b/2024/21/run.ts @@ -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); diff --git a/utils/multi-map.ts b/utils/multi-map.ts index c356d3c..5d85e59 100644 --- a/utils/multi-map.ts +++ b/utils/multi-map.ts @@ -24,5 +24,6 @@ export class MultiMap extends Map { } else { this.set(firstKey, value); } + return value; } }