135 lines
3.6 KiB
JavaScript
135 lines
3.6 KiB
JavaScript
const fs = require('fs/promises');
|
|
|
|
const sum = (arr) => arr.reduce((total, num) => total+num, 0);
|
|
const arrayOfLength = n => Array.from(Array(n).keys()).map(() => {});
|
|
|
|
const createNode = (str) => {
|
|
const stats = str.match(/Valve ([A-Z]+) has flow rate=([\d]+); tunnels? leads? to valves? (.*)$/);
|
|
if(!stats) {
|
|
console.log("Bad line? : ", str);
|
|
return;
|
|
}
|
|
|
|
const id = stats[1];
|
|
const rate = stats[2];
|
|
const edges = stats[3].split(', ');
|
|
|
|
return {
|
|
id,
|
|
rate,
|
|
edges,
|
|
};
|
|
};
|
|
|
|
|
|
const main = async () => {
|
|
const input = (await fs.readFile("./input.txt", 'utf8')).split("\n").slice(0, -1).sort();
|
|
|
|
const nodesById = {};
|
|
|
|
for(const row of input) {
|
|
const node = createNode(row);
|
|
if(!node) continue;
|
|
nodesById[node.id] = node;
|
|
}
|
|
|
|
const nodesByRate = Object.values(nodesById).sort((a, b) => b.rate - a.rate).filter(n => n.rate > 0);
|
|
|
|
|
|
const findEachOptimalPath = (node, queue = [], visited = {}, path = []) => {
|
|
visited[node.id] = path;
|
|
for(const edge of node.edges) {
|
|
const neighbor = nodesById[edge];
|
|
if(typeof visited[neighbor.id] === "undefined") {
|
|
const nextPath = [...path, neighbor];
|
|
queue.push(() => findEachOptimalPath(neighbor, queue, visited, nextPath));
|
|
}
|
|
};
|
|
if(queue.length) {
|
|
return queue.shift()();
|
|
}
|
|
return visited;
|
|
};
|
|
|
|
|
|
const valvePaths = Object.values(nodesById).reduce((possibilities, node) => {
|
|
possibilities[node.id] = findEachOptimalPath(node);
|
|
return possibilities;
|
|
}, {});
|
|
|
|
|
|
let best = 0;
|
|
const bestPerTime = [];
|
|
|
|
let count = 0;
|
|
const perfectPlan = (node1, node2, target1, target2, value = 0, targeted = {"AA": true}, time = 26) => {
|
|
if(time < 1) {
|
|
return;
|
|
}
|
|
count++;
|
|
if(!(count % 2000000)) {
|
|
console.log("Evaluated ", count, "possible minutes");
|
|
}
|
|
let acted1 = false;
|
|
let acted2 = false;
|
|
if(target1 === node1) {
|
|
target1 = undefined;
|
|
acted1 = true;
|
|
value = value + (time - 1) * node1.rate;
|
|
}
|
|
|
|
if(target2 === node2) {
|
|
target2 = undefined;
|
|
acted2 = true;
|
|
value = value + (time - 1) * node2.rate;
|
|
}
|
|
|
|
if(value > best) {
|
|
best = value;
|
|
console.log(`${best} is better!`);
|
|
}
|
|
if(!bestPerTime[time] || bestPerTime[time] <= value) {
|
|
bestPerTime[time] = value;
|
|
}
|
|
//1.5 seems to work. Higher values work faster, but risk missing the best path
|
|
const lowBar = bestPerTime[Math.floor(time + time/1.5)];
|
|
if(lowBar && lowBar > value) {
|
|
return;
|
|
}
|
|
|
|
const paths1 = valvePaths[node1.id];
|
|
const paths2 = valvePaths[node2.id];
|
|
|
|
const possibleNodes = nodesByRate.filter(n => !targeted[n.id]);
|
|
|
|
for(const next1 of possibleNodes) {
|
|
for(const next2 of possibleNodes) {
|
|
if(next1 === next2) {
|
|
continue;
|
|
}
|
|
const n1 = acted1 ? node1 : target1 ? paths1[target1.id][0] : paths1[next1.id][0];
|
|
const n2 = acted2 ? node2 : target2 ? paths2[target2.id][0] : paths2[next2.id][0];
|
|
const t1 = acted1 ? null : target1 || next1;
|
|
const t2 = acted2 ? null : target2 || next2;
|
|
if(t2 && t1 && paths1[t2.id].length < paths2[t2.id].length && paths2[t1.id].length < paths2[t2.id].length) {
|
|
continue;
|
|
}
|
|
const newTargeted = {...targeted};
|
|
if(t1) {
|
|
newTargeted[t1.id] = true;
|
|
}
|
|
if(t2) {
|
|
newTargeted[t2.id] = true;
|
|
}
|
|
perfectPlan(n1, n2, t1, t2, value, {...newTargeted}, time - 1);
|
|
if(target2 || acted2) break;
|
|
}
|
|
if(target1 || acted1) break;
|
|
}
|
|
};
|
|
|
|
perfectPlan(nodesById["AA"], nodesById["AA"]);
|
|
console.log("Answer is: ", best);
|
|
}
|
|
|
|
main();
|