diff --git a/src/day_eight.test.ts b/src/day_eight.test.ts index 4b0ecad..92346fa 100644 --- a/src/day_eight.test.ts +++ b/src/day_eight.test.ts @@ -1,4 +1,4 @@ -import {DesertMap} from "./day_eight"; +import {DesertMap, lcm} from "./day_eight"; describe('Day Eight', () => { const input = `RL @@ -17,6 +17,17 @@ AAA = (BBB, BBB) BBB = (AAA, ZZZ) ZZZ = (ZZZ, ZZZ)` + const ghostInput = `LR + +11A = (11B, XXX) +11B = (XXX, 11Z) +11Z = (11B, XXX) +22A = (22B, XXX) +22B = (22C, 22C) +22C = (22Z, 22Z) +22Z = (22B, 22B) +XXX = (XXX, XXX)` + it('should calculate the number of steps needed to reach ZZZ', () => { const map = new DesertMap(input); expect(map.stepsTo('ZZZ')).toEqual(2); @@ -25,5 +36,16 @@ ZZZ = (ZZZ, ZZZ)` it('should repeat the pattern', () => { const map = new DesertMap(repeatedInput); expect(map.stepsTo('ZZZ')).toEqual(6); - }) + }); + + it('should count how many steps it takes to get from every node beginning with A, to every node ending in Z simultaneously', () => { + const map = new DesertMap(ghostInput); + expect(map.ghostStepsToZ()).toEqual(6); + }); + + describe('lcm', () => { + it('should get the correct lowest common multiple', () => { + expect(lcm(12, 30)).toEqual(60); + }); + }); }); \ No newline at end of file diff --git a/src/day_eight.ts b/src/day_eight.ts index 254029f..d05bc6e 100644 --- a/src/day_eight.ts +++ b/src/day_eight.ts @@ -1,10 +1,11 @@ -import {anyCharOf, newline, uniLetter, whitespace} from "parjs"; -import {between, exactly, manySepBy, manyTill, stringify, then} from "parjs/combinators"; +import {anyCharOf, int, newline, uniDecimal, uniLetter, whitespace} from "parjs"; +import {between, exactly, manySepBy, manyTill, or, stringify, then} from "parjs/combinators"; import fs from "fs"; +import { Set } from 'immutable'; const patternParser = anyCharOf("LR").pipe(manyTill(newline().pipe(exactly(2)))); -const nodeNameParser = uniLetter().pipe(exactly(3), stringify()); +const nodeNameParser = uniLetter().pipe(or(uniDecimal()), exactly(3), stringify()); const childParser = nodeNameParser.pipe(manySepBy(", "), exactly(2), between("(", ")")); const nodeParser = nodeNameParser.pipe(then(childParser.pipe(between(" = ", whitespace())))) @@ -16,6 +17,16 @@ type Instruction = "L" | "R"; type NodeName = string; type NodeChildren = [Maybe, Maybe]; +const gcd = (a: number, b: number): number => { + if (b === 0) return a; + return gcd(b, a % b); +} + +export const lcm = (a: number, b: number): number => { + const product = a * b; + return product / gcd(a, b); +} + export class DesertMap { private readonly pattern: Instruction[]; @@ -30,7 +41,7 @@ export class DesertMap { if (!this.map[name]) { this.map[name] = [undefined, undefined]; } - const children = [leftNode !== name ? leftNode : undefined, rightNode !== name ? rightNode : undefined]; + const children = [leftNode, rightNode]; this.map[name] = children as NodeChildren; } } @@ -56,11 +67,46 @@ export class DesertMap { } return step; } + + public stepsToZ(from: string): number { + let step = 0; + let curr = from; + + while (!curr.endsWith('Z')) { + const instruction = this.pattern[step % this.pattern.length]; + + const [left, right] = this.map[curr]; + + if (instruction === "L" && left) { + curr = left; + } else if (instruction === "R" && right) { + curr = right; + } + + if (!curr) return 0; + + step++; + } + return step; + } + + private isComplete(keys: string[]) { + return keys.every(key => key.endsWith('Z')); + } + + public ghostStepsToZ(): number { + let keys = Object.keys(this.map).filter(key => key.endsWith('A')); + + return keys.map(key => this.stepsToZ(key)).reduce(lcm, 1); + } } + 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')); + + console.log(map.ghostStepsToZ()); } \ No newline at end of file