UNPKG

expeditaet

Version:
192 lines (165 loc) 5.37 kB
import { Direction, GridGraph, GridGraphNode, task, type ToString, } from '@alexaegis/advent-of-code-lib'; import packageJson from '../package.json'; import { parse, weighter } from './parse.js'; export const findPartition = < T extends ToString = string, N extends GridGraphNode<T> = GridGraphNode<T>, >( partitions: Map<N, number>[], node: N, ): Map<N, number> => { const partition = partitions.find((p) => p.has(node)); if (!partition) { throw new Error('No partition found for node'); } return partition; }; const enclosedPartitions = < T extends ToString = string, N extends GridGraphNode<T> = GridGraphNode<T>, >( partitions: Map<N, number>[], encloser: Map<N, number>, ): Map<N, number>[] => { const partitionsEnclosedByLoop = partitions.filter( (partition) => partition !== encloser && partition.keyArray().every( (pn) => pn.neighbours.size >= 4 && // Not an edge node pn.neighbours .valueArray() .every((pnn) => encloser.has(pnn.to) || partition.has(pnn.to)), ), ); return partitionsEnclosedByLoop; }; export const partitionGraph = < T extends ToString = string, N extends GridGraphNode<T> = GridGraphNode<T>, >( graph: GridGraph<T, N>, foundPartitions: Map<N, number>[], ): Map<N, number>[] => { const nonPartitionedNodes = new Set(graph.nodes.values()); for (const foundPartition of foundPartitions) { for (const [foundNode] of foundPartition) { nonPartitionedNodes.delete(foundNode); } } while (nonPartitionedNodes.size > 0) { const nodeNotInTheLoop = nonPartitionedNodes.values().next().value as N; const nextPartition = graph.flood({ start: nodeNotInTheLoop }); for (const [foundNode] of nextPartition) { nonPartitionedNodes.delete(foundNode); } foundPartitions.push(nextPartition); } return foundPartitions; }; export interface GraphLoopPartitions< T extends ToString = string, N extends GridGraphNode<T> = GridGraphNode<T>, > { loop: Set<N>; /** * empty if the loop has no nodes inside, or the loop is not closed */ inside: Set<N>; /** * empty if the loop has no nodes outside, or the loop is not closed */ outside: Set<N>; } export const partitionIntoTwoFromLoop = < T extends ToString = string, N extends GridGraphNode<T> = GridGraphNode<T>, >( graph: GridGraph<T, N>, loop: Map<N, number>, ): GraphLoopPartitions<T, N> => { const nonPartitionedNodes = new Set(graph.nodes.values()); for (const [foundNode] of loop) { nonPartitionedNodes.delete(foundNode); } const foundPartitions: Map<N, number>[] = []; while (nonPartitionedNodes.size > 0) { const nodeNotInTheLoop = nonPartitionedNodes.values().next().value as N; const nextPartition = graph.flood({ start: nodeNotInTheLoop, currentPathWeighter: (a, b) => loop.has(a) || loop.has(b) ? Number.POSITIVE_INFINITY : 1, }); for (const [foundNode] of nextPartition) { nonPartitionedNodes.delete(foundNode); } foundPartitions.push(nextPartition); } const insides = enclosedPartitions<T, N>(foundPartitions, loop); const outside = foundPartitions.find((p) => !insides.includes(p)); return { loop: new Set(loop.keys()), inside: new Set(insides.flatMap((i) => i.keyArray())), outside: new Set(outside?.keys()), }; }; export const p2 = (input: string): number => { const originalGraph = parse(input); const liminalNodes = new Set<GridGraphNode>(); // Expanding graph to address liminal space const graph = new GridGraph<string>(); for (const ogNode of originalGraph.nodeValues) { for (const { from, to, weight, direction } of Direction.allDirections.map((direction) => ({ ...ogNode.neighbours.get(direction), from: ogNode, to: originalGraph.getNode(ogNode.coordinate.add(direction)), direction, }))) { if (to === undefined) { continue; } let newNodeSymbol = '.'; if (from.value === '.' && to.value === '.') { newNodeSymbol = '.'; } else if (weight && weight < Number.POSITIVE_INFINITY) { newNodeSymbol = direction.isHorizonal() ? '-' : '|'; } const fromDoubled = from.coordinate.add(from.coordinate); const toDoubled = to.coordinate.add(to.coordinate); const middle = fromDoubled.middle(toDoubled); const newFrom = graph.nodes.getOrAdd( fromDoubled.toString(), (_n) => new GridGraphNode(fromDoubled, from.value), ); const newMiddle = graph.nodes.getOrAdd( middle.toString(), (_n) => new GridGraphNode(middle, newNodeSymbol), ); liminalNodes.add(newMiddle); const newTo = graph.nodes.getOrAdd( toDoubled.toString(), (_n) => new GridGraphNode(toDoubled, to.value), ); newFrom.attachNeightbours(graph, Direction.cardinalDirections, weighter); newMiddle.attachNeightbours(graph, Direction.cardinalDirections, weighter); newTo.attachNeightbours(graph, Direction.cardinalDirections, weighter); } } const animalStart = graph.findNode((node) => node.value === 'S'); if (!animalStart) { throw new Error('no starting position for the animal!'); } const pathLoop = graph.flood({ start: animalStart }); const { inside, outside } = partitionIntoTwoFromLoop(graph, pathLoop); if (process.env['RUN']) { graph.print((n) => (inside.has(n) ? 'I' : outside.has(n) ? 'O' : n.toString())); } return inside.valueArray().filter((node) => !liminalNodes.has(node)).length ?? -1; }; await task(p2, packageJson.aoc); // 435 ~1.4s