UNPKG

expeditaet

Version:
171 lines (156 loc) 4.71 kB
/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { Graph, GraphNode, memoize, task } from '@alexaegis/advent-of-code-lib'; import packageJson from '../package.json'; import { parse, type Valve } from './parse.function.js'; const TIME_LIMIT = 30; const pathBetweenValves = memoize( ( fromValve: string, toValve: string, graph: Graph<Valve, string>, ): GraphNode<Valve, string>[] => { const from = graph.getNode(fromValve); if (!from) { throw new Error('no start valve'); } const to = graph.getNode(toValve); return graph.aStar({ start: from, end: to }).path; }, ); /** * A valves score is how much pressure it will release until min30, if we start * go there now. (So (30 - currentMin - distance) * flowRate * * Well, this is at most a heuristic */ const scoreValve = ( currentTime: number, openableValvesAfterThis: Valve[], openValves: Valve[], fromValve: Valve, toValve: Valve, graph: Graph<Valve, string>, depth = 0, ): number => { if (depth === 0) { return 0; } else { const nextPath = pathBetweenValves(fromValve.name, toValve.name, graph); const nextTarget = nextPath.last(); const distance = nextPath.length - 1; const cumulative = highestScoreValve( currentTime + distance + 1, nextTarget.value, openValves, openableValvesAfterThis, graph, depth - 1, )?.score ?? 0; // for each unopened worthvile - the fromtarget return cumulative + (TIME_LIMIT - currentTime - distance) * toValve.flowRate; } }; const highestScoreValve = ( currentTime: number, fromValve: Valve, openValves: Valve[], valves: Valve[], graph: Graph<Valve, string>, depth: number, ): { valve: Valve; score: number } | undefined => { const openableValves = valves.filter( (valve) => valve.flowRate > 0 && !openValves.includes(valve), ); const valvesSortedByScore = openableValves .map((valve) => ({ valve, score: scoreValve( currentTime, openableValves.filter((v) => v !== valve), [...openValves, valve], fromValve, valve, graph, depth, ), })) .sort((a, b) => b.score - a.score); // console.log( // 'valvesSortedByScore', // currentTime, // 'from', // fromValve.name, // valvesSortedByScore.map((v) => v.valve.name + ': ' + v.score).join('; ') // ); return valvesSortedByScore.length > 0 ? valvesSortedByScore[0] : undefined; }; /*** * Backtracking algorithm is needed */ export const p1 = (input: string): number => { const valves = parse(input); const graph = new Graph<Valve, string>(); for (const valve of valves) { graph.nodes.set(valve.name, new GraphNode(valve.name, valve)); } for (const valve of valves) { const from = graph.nodes.get(valve.name)!; for (const lead of valve.leadsTo) { const to = graph.nodes.get(lead)!; from.neighbours.set(lead, { from, to, weight: 0, direction: lead }); } } let pressureReleasedSoFar = 0; const valvesWorthOpening = valves.filter((valve) => valve.flowRate > 0); const openedValves: Valve[] = []; let movingAlongPath: Valve[] | undefined = undefined; let currentlyAtValve: Valve = valves.find((valve) => valve.name === 'AA')!; // Start at AA let targetValve: Valve | undefined = undefined; for (let i = 0; i < TIME_LIMIT; i++) { // calc pressure const pressureThisRound = openedValves.map((valve) => valve.flowRate).sum(); pressureReleasedSoFar += pressureThisRound; // console.log(`\n== Minute ${i + 1} ==`); if (openedValves.length > 0) { // console.log( // `Valves ${openedValves // .map((v) => v.name) // .join(', ')} are open, releasing ${pressureThisRound} pressure.`, // ); } else { console.log('No valves are open.'); } if (!targetValve && openedValves.length < valvesWorthOpening.length) { targetValve = highestScoreValve( i, currentlyAtValve, openedValves, valves, graph, 4, )?.valve; if (targetValve) { // console.log('TARGET', targetValve.name); movingAlongPath = pathBetweenValves( currentlyAtValve.name, targetValve.name, graph, ).map((node) => node.value); movingAlongPath.shift()!; // get rid of starting value } else { throw new Error('Should still be able to find an opened valve and target it'); } } if (movingAlongPath?.length) { currentlyAtValve = movingAlongPath.shift()!; // console.log(`You move to valve ${currentlyAtValve.name}`); } else if (currentlyAtValve === targetValve && !openedValves.includes(currentlyAtValve)) { openedValves.push(currentlyAtValve); targetValve = undefined; // console.log(`You open valve ${currentlyAtValve.name}`); } } return pressureReleasedSoFar; }; await task(p1, packageJson.aoc); // 1580 ~0ms