Advent-of-Code-2022/16/solution.mjs

153 lines
3.9 KiB
JavaScript
Raw Normal View History

2022-12-19 16:13:00 -08:00
import { readFileSync } from 'node:fs';
const input = readFileSync('input', 'utf-8');
const valves = new Map(input
.split('\n')
.filter(Boolean)
.map(line => {
const valve = line.match(/Valve (\w+)/)[1];
const rate = parseInt(line.match(/rate=(\d+)/)[1]);
const to = line.match(/valves? (.*)/)[1].split(', ');
return [valve, {valve, rate, to}];
})
);
function shortestPath(v, vDest, path=[]) {
const valve = valves.get(v);
if (valve.to.includes(vDest)) {
return [...path, v, vDest];
}
return valve.to
.filter(to => !path.includes(to))
.map(to => shortestPath(to, vDest, [...path, v]))
.filter(Boolean)
.sort((p1, p2) => p1.length - p2.length)[0];
}
// Add a property to all valves containing links to all other valves with a
// flow rate greater than 0 and the time it will take to reach them
const valvesWithFlow = [...valves.values()]
.filter(valve => valve.rate > 0);
for (const valve of valves.values()) {
valve.usefulTo = valvesWithFlow
.filter(vDest => vDest.valve !== valve.valve)
.map(vDest => ({
valve: vDest.valve,
time: shortestPath(valve.valve, vDest.valve).length - 1,
rate: vDest.rate
}));
}
function product(...arrs) {
if (!arrs.length) return [];
return arrs.reduce((result, arr) =>
result.flatMap(r =>
arr.map(a => [...r, a])
),
[[]]
)
}
// General solution for n players
function solve(players, rate=0) {
for (const player of players) {
const valve = valves.get(player.path[player.path.length - 1]);
if (valve.rate > 0 && !player.opened) {
player.opened = true;
player.timeRemaining -= 1;
rate += valve.rate * Math.max(0, player.timeRemaining);
}
}
// Array of valve names which haven't been visited
const unvisitedValves = valvesWithFlow
.map(v => v.valve)
.filter(valve => !players.find(p => p.path.includes(valve)));
// Exit conditions
if (
unvisitedValves.length === 0 ||
players.every(player => player.timeRemaining <= 0)
) {
return [rate, players];
}
// Decide who is moving
const maxTimeRemaining = players.reduce(
(max, player) => Math.max(max, player.timeRemaining),
0
);
const movingPlayers = players.filter(
player => player.timeRemaining === maxTimeRemaining
);
const nonMovingPlayers = players.filter(
player => !movingPlayers.includes(player)
);
const playerMoves = product(...movingPlayers
.map(player => {
const valve = valves.get(player.path[player.path.length - 1]);
const destinations = valve.usefulTo
.filter(to => unvisitedValves.includes(to.valve))
.filter((to, _, tos) => !tos.find((otherTo) =>
otherTo.rate > to.rate &&
otherTo.time <= to.time
));
if (destinations.length < movingPlayers.length) {
destinations.push(undefined);
}
return destinations;
})
).filter(moves => {
const valves = moves.filter(Boolean).map(to => to.valve);
return (
valves.length !== 0 &&
new Set(valves).size === valves.length
);
});
return playerMoves
.map(moves => {
const movedPlayers = moves.map((to, i) => {
const player = movingPlayers[i];
if (to === undefined) {
// Not moving
return player;
}
return {
timeRemaining: player.timeRemaining - to.time,
opened: false,
path: [...player.path, to.valve]
};
});
return solve([...nonMovingPlayers, ...movedPlayers], rate);
})
.filter(Boolean)
.sort(([rate1], [rate2]) => rate2 - rate1)[0];
}
const solution1 = solve([{
timeRemaining: 30,
opened: false,
path: ['AA']
}]);
console.log(`Max pressure released in 30 minutes: ${solution1[0]}`);
const solution2 = solve([{
timeRemaining: 26,
opened: false,
path: ['AA']
}, {
timeRemaining: 26,
opened: false,
path: ['AA']
}]);
console.log(`Max pressure released with elephant: ${solution2[0]}`);