UNPKG

expeditaet

Version:
228 lines (201 loc) 7.02 kB
/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { task } from '@alexaegis/advent-of-code-lib'; import packageJson from '../package.json'; import { parse, type Valve } from './parse.function.js'; const OPEN = 'open'; const WAIT = 'wait'; type PathSegment = Valve | typeof OPEN | typeof WAIT; type Path = PathSegment[]; interface ValveNode { path: Path; pressureReleasedSoFar: number; openValves: Valve[]; // edgesVisited: string[]; /** * Not worth going because in that direction everything is either opened * or not worth opening */ forbiddenEdges: string[]; forbiddenValves: string[]; } const normalizeOption = (key: string, valveMap: Map<string, Valve>): PathSegment => { return key === OPEN || key === WAIT ? key : valveMap.get(key)!; }; const isValveSegment = (segment: PathSegment): segment is Valve => { return typeof segment === 'object'; }; const serializePath = (path: Path): string => { return path.map((segment) => (typeof segment === 'object' ? segment.name : segment)).join(','); }; /*** * Backtracking algorithm is needed */ export const p1 = (input: string): number => { const valves = parse(input); const valveMap = new Map<string, Valve>(); for (const valve of valves) { valveMap.set(valve.name, valve); } const valvesWorthOpening = valves.filter((valve) => valve.flowRate > 0); /** * This map contains edges where the keys are paths like "AA,open,CC,II,wait" * To get the previous one, pop one off the path and calculate it's key * * To get destinations calculate it from the last path element * * Once the graph is filled filter out all paths that are marked as end * * And there's some other metadata specific to the task, * * graphSpace cache */ const solutionGraph = new Map<string, ValveNode>(); // Start at "AA" /** * solutionSpace cache */ let currentSolution: ValveNode | undefined = { path: [valveMap.get('AA')!], pressureReleasedSoFar: 0, openValves: [], forbiddenEdges: [], forbiddenValves: [], }; solutionGraph.set('AA', currentSolution); const _expectedPathForExample = 'AA,DD,open,CC,BB,open,AA,II,JJ,open,II,AA,DD,EE,FF,GG,HH,open,GG,FF,EE,open,DD,CC,open,wait,wait,wait...'; let i = 0; console.log('ohboy'); while (currentSolution) { const currentValve = currentSolution.path.findLast(isValveSegment)!; const currentPath = currentSolution.path; const currentPathSerialized = serializePath(currentSolution.path); const currentlyForbiddenEdges = currentSolution.forbiddenEdges; const currentlyForbiddenValves = currentSolution.forbiddenValves; i++; /** * && currentPathSerialized.startsWith('AA,DD,open,CC,BB,open,AA,II,JJ,open,II,AA') */ if ( i % 1 === 0 && currentPathSerialized.startsWith('AA,DD,open,CC,BB,open,AA,II,JJ,open,II,AA') ) { console.log( i, 'Checking solution', currentPathSerialized, 'time passed', currentSolution.path.length, 'openValves', currentSolution.openValves.map((v) => v.name).join(','), 'forbiddenEdges', currentSolution.forbiddenEdges.join('; '), 'pressure released', currentSolution.pressureReleasedSoFar, ); } const nextOptions: string[] = []; // It's its worth opening and wasn't already opened this round, or this path wasnt tried if ( currentValve !== 'open' && currentValve !== 'wait' && currentValve.flowRate > 0 && !currentSolution.openValves.contains(currentValve) && !solutionGraph.has(`${currentPathSerialized},${OPEN}`) ) { nextOptions.push(OPEN); } const nextForbiddenValves = [...currentlyForbiddenValves]; if (currentSolution.openValves.length === valvesWorthOpening.length) { // Everything is open, the only thing left is waiting nextOptions.push(WAIT); } else if (currentValve !== 'open' && currentValve !== 'wait') { const notCoveredValves = currentValve.leadsTo.filterMap((valveKey) => { const wouldBeSolution = `${currentPathSerialized},${valveKey}`; const graphAlreadyCoversSolution = solutionGraph.has(wouldBeSolution); return !currentlyForbiddenValves.includes(valveKey) && !graphAlreadyCoversSolution ? valveKey : undefined; }); const strictNotCoveredEdges = notCoveredValves.filter((valveKey) => { const wouldBeEdge = `${currentValve.name},${valveKey}`; return !currentPathSerialized.includes(wouldBeEdge); }); if (strictNotCoveredEdges.length > 0) { nextOptions.push(...strictNotCoveredEdges); } else if (notCoveredValves.length > 0) { nextOptions.push(...notCoveredValves); } else { nextForbiddenValves.push(currentValve.name); } } if (currentPath.length > 30) { nextOptions.clear(); } let backtrack = false; if (nextOptions.length > 0) { // If there is somewhere to go, try the first option const nextOption = normalizeOption(nextOptions[0]!, valveMap); const nextPath = [...currentPath, nextOption]; const nextPathSerialized = serializePath(nextPath); //if (isValveSegment(nextOption)) { // const nextEdge = `${currentValve.name},${nextOption.name}`; // // nextAlreadyWalked.push(nextEdge); //} // release pressure const releasedThisRound = currentSolution.openValves .map((valve) => valve.flowRate) .sum(); const openValves = [...currentSolution.openValves]; if ( currentValve !== 'open' && currentValve !== 'wait' && nextOption === OPEN && !openValves.includes(currentValve) ) { openValves.push(currentValve); } if (solutionGraph.has(nextPathSerialized)) { backtrack = true; } else { const nextSolution: ValveNode = { path: nextPath, openValves, pressureReleasedSoFar: currentSolution.pressureReleasedSoFar + releasedThisRound, forbiddenEdges: currentlyForbiddenEdges, forbiddenValves: nextForbiddenValves, }; solutionGraph.set(nextPathSerialized, nextSolution); currentSolution = nextSolution; } } else { backtrack = true; } if (backtrack) { // backtrack const prev = currentSolution.path.clone(); prev.pop(); const prevPathKey = serializePath(prev); // If we try to backtrack from the starting, 1 long path, we get an // empty key, currentNode gets set to undefined and the while loop breaks currentSolution = solutionGraph.get(prevPathKey); } } const finishedSolutions = solutionGraph .valueArray() .filter((solution) => solution.path.length > 30) .sort((a, b) => b.pressureReleasedSoFar - a.pressureReleasedSoFar); console.log('finishedSolutions', finishedSolutions.length); const solutionWithMostReleasedPressure = finishedSolutions[0]; const serializedSolutionPath = serializePath(solutionWithMostReleasedPressure!.path); console.log( 'solutionWithMostReleasedPressure', serializedSolutionPath, 'is it correct for the exmaple?', serializedSolutionPath.startsWith(_expectedPathForExample), ); return finishedSolutions[0]!.pressureReleasedSoFar; }; await task(p1, packageJson.aoc, 'example.1.txt'); // 0 ~0ms