diff --git a/index.ts b/index.ts index 567351e..e809564 100644 --- a/index.ts +++ b/index.ts @@ -1,3 +1,3 @@ -import {runDaySeven} from "./src/day_seven"; +import {runDayEight} from "./src/day_eight"; -runDaySeven(); \ No newline at end of file +runDayEight(); \ No newline at end of file diff --git a/src/day_eight.test.ts b/src/day_eight.test.ts new file mode 100644 index 0000000..4b0ecad --- /dev/null +++ b/src/day_eight.test.ts @@ -0,0 +1,29 @@ +import {DesertMap} from "./day_eight"; + +describe('Day Eight', () => { + const input = `RL + +AAA = (BBB, CCC) +BBB = (DDD, EEE) +CCC = (ZZZ, GGG) +DDD = (DDD, DDD) +EEE = (EEE, EEE) +GGG = (GGG, GGG) +ZZZ = (ZZZ, ZZZ)`; + + const repeatedInput = `LLR + +AAA = (BBB, BBB) +BBB = (AAA, ZZZ) +ZZZ = (ZZZ, ZZZ)` + + it('should calculate the number of steps needed to reach ZZZ', () => { + const map = new DesertMap(input); + expect(map.stepsTo('ZZZ')).toEqual(2); + }); + + it('should repeat the pattern', () => { + const map = new DesertMap(repeatedInput); + expect(map.stepsTo('ZZZ')).toEqual(6); + }) +}); \ No newline at end of file diff --git a/src/day_eight.ts b/src/day_eight.ts new file mode 100644 index 0000000..6743379 --- /dev/null +++ b/src/day_eight.ts @@ -0,0 +1,126 @@ +import {anyCharOf, newline, rest, uniLetter, whitespace} from "parjs"; +import {then, many, manyTill, exactly, between, manySepBy, stringify} from "parjs/combinators"; +import fs from "fs"; +import {CamelCards} from "./day_seven"; + +const patternParser = anyCharOf("LR").pipe(manyTill(newline().pipe(exactly(2)))); + +const nodeNameParser = uniLetter().pipe(exactly(3), stringify()); +const childParser = nodeNameParser.pipe(manySepBy(", "), exactly(2), between("(", ")")); +const nodeParser = nodeNameParser.pipe(then(childParser.pipe(between(" = ", whitespace())))) + +const parser = patternParser.pipe(then(nodeParser.pipe(manySepBy(whitespace())))); + +class Node { + constructor( + public label: string, + public _left?: Node, + public _right?: Node + ) { + if (this._left?.label === label) this._left = undefined; + if (this._right?.label === label) this._right = undefined; + } + + public set left(node: Node) { + if (node.label !== this.label) { + this._left = node; + } + } + + public set right(node: Node) { + if (node.label !== this.label) { + this._right = node; + } + } + + public get left(): Node | undefined { + return this._left; + } + + public get right(): Node | undefined { + return this._right; + } + + public get isBranch(): boolean { + return Boolean(this.left || this.right); + } + + /** + * Find a node by label using basic Depth-first search + */ + public find(label: string, searchedNodes: string[] = []): Node | undefined { + if (this.label === label) return this; + if (searchedNodes.includes(this.label)) return; + + if (this.left) { + const leftSearch = this.left.find(label, [...searchedNodes, this.label]); + if (leftSearch) return leftSearch; + } + + if (this.right) { + const rightSearch = this.right.find(label, [...searchedNodes, this.label]); + if (rightSearch) return rightSearch; + } + } +} + +type Instruction = "L" | "R"; + +export class DesertMap { + private readonly pattern: Instruction[]; + private tree?: Node; + + constructor(input: string) { + const [pattern, nodes] = parser.parse(input).value; + + this.pattern = pattern as Instruction[]; + + for (const [name, [[leftNode, rightNode]]] of nodes) { + if (!this.tree) { + this.tree = new Node(name); + } + + const parent = this.tree.find(name); + + if (!parent) { + console.log(`No parent found for label ${name}`) + } else { + + const foundLeft = this.tree.find(leftNode); + parent.left = foundLeft || new Node(leftNode); + + const foundRight = this.tree.find(rightNode); + parent.right = foundRight || new Node(rightNode); + } + } + } + + public stepsTo(node: string): number { + if (!this.tree) return 0; + + let step = 0; + let curr = this.tree; + + while (curr.label !== node) { + const instruction = this.pattern[step % this.pattern.length]; + + if (instruction === "L" && curr.left) { + curr = curr.left; + } else if (instruction === "R" && curr.right) { + curr = curr.right; + } + + if (!curr) return 0; + + step++; + } + return step; + } +} + +export const runDayEight = () => { + const input = fs.readFileSync('./inputs/day_eight_input.txt', 'utf-8').trimEnd(); + + const map = new DesertMap(input); + console.log(map.stepsTo('ZZZ')); +} \ No newline at end of file