advent-of-code

Entries to advent of code, multiple years
git clone git://git.finwo.net/misc/advent-of-code
Log | Files | Refs

index.js (4400B)


      1 const fs           = require('fs');
      2 const through      = require('through2');
      3 const line_by_line = require('../common/line-for-line');
      4 
      5 String.prototype.startsWith = function(subject) {
      6   return this.substr(0, subject.length) === subject;
      7 };
      8 
      9 const valves = {};
     10 const map    = {};
     11 const nmap   = {};
     12 const rmap   = [];
     13 
     14 
     15 // Consistent-cost Dijkstra
     16 function costFinder(from, to) {
     17   const cost = Object.keys(valves)
     18     .reduce((r, key) => {
     19       r[key] = Infinity;
     20       return r;
     21     }, {});
     22   cost[from] = 0;
     23   const openSet = [from];
     24   while(openSet.length) {
     25     openSet.sort((a,b) => a.cost - b.cost);
     26     const active = openSet.shift();
     27     for(const next of valves[active].tunnels) {
     28       const nCost = cost[active]+1;
     29       if (next == to) {
     30         return nCost;
     31       }
     32       if (nCost < cost[next]) {
     33         cost[next] = nCost;
     34         openSet.push(next);
     35       }
     36     }
     37   }
     38   return cost[to]
     39 }
     40 
     41 fs.createReadStream('input')
     42   .pipe(line_by_line())
     43 
     44   // Minor parsing
     45   .pipe(through.obj(function(line, enc, cb) {
     46     line = line.toString();
     47     if (!line) return cb();
     48     const valve = { state: false, total: 0 };
     49     const parts = line.split(' ');
     50     parts.shift();
     51     valve.id = parts.shift();
     52     parts.shift();
     53     parts.shift();
     54     valve.rate = parseInt(parts.shift().replace(/[^\d\-]/g, ''));
     55     parts.shift();
     56     parts.shift();
     57     parts.shift();
     58     parts.shift();
     59     valve.tunnels = parts.map(s => s.replace(/,/g,''));
     60     valves[valve.id] = valve;
     61     cb();
     62   }))
     63 
     64   .on('finish', () => {
     65     console.log('loaded');
     66 
     67     // Precalculate cost between all valves
     68     const COST = {};
     69     for(let src of Object.keys(valves)) {
     70       for(let dst of Object.keys(valves)) {
     71         COST[src+dst] = costFinder(src, dst);
     72       }
     73     }
     74 
     75     // Remove non-interesting valves
     76     for(const valve of Object.values(valves)) {
     77       if(valve.id == 'AA') continue;
     78       if (valve.rate) continue;
     79       delete valves[valve.id];
     80     }
     81 
     82     // Name to value map
     83     let i = 1;
     84     let targets = 0;
     85     for(const name of Object.keys(valves)) {
     86       nmap[name] = i;
     87       rmap[i] = name;
     88       targets += i;
     89       i *= 2;
     90     }
     91 
     92     // Initialize solution finder
     93     const totalTime   = 26;
     94     const actorCount  = 2;
     95     let   maxPressure = 0;
     96     let   timer       = 0;
     97     let   minlen      = Infinity;
     98     const openStates  = [
     99       {
    100         valves: nmap['AA'],
    101         actors: new Array(actorCount).fill(0).map(() => ({ location: 'AA', time: 0 })),
    102         pressure: 0,
    103       }
    104     ];
    105 
    106     map[nmap['AA'].toString(2)] = 0;
    107 
    108     while(openStates.length) {
    109       if (openStates.length < minlen) minlen = openStates.length;
    110       if (--timer < 0) { timer = 1e5; console.log(minlen); minlen = Infinity; }
    111 
    112       const state = openStates.pop();
    113 
    114       // Track max pressure released
    115       if (state.pressure > maxPressure) {
    116         maxPressure = state.pressure;
    117         console.log({ maxPressure });
    118       }
    119 
    120       // All valves opened, no further work on this path
    121       if (state.valves == targets) {
    122         continue;
    123       }
    124 
    125       for(const idx in state.actors) {
    126         const actor = state.actors[idx];
    127         // Skip current actor if over-time
    128         if (actor.time > totalTime) continue;
    129         // Emulate move + valve open
    130         for(i=1; i<targets; i*=2) {
    131           // Don't attempt valves already opened
    132           if (state.valves & i) continue;
    133           // Calculate cost & result of moving+opening currently selected valve
    134           const cost     = COST[actor.location + rmap[i]] + 1;
    135           const aCost    = actor.time + cost;
    136           const pressure = (totalTime - aCost) * valves[rmap[i]].rate;
    137           const result   = state.valves ^ i;
    138           if (aCost > totalTime) continue;
    139           // Push to open states
    140           const newPressure = state.pressure + ((totalTime-aCost) * valves[rmap[i]].rate);
    141           const newActors = [...state.actors];
    142           newActors[idx] = {
    143             location: rmap[i],
    144             time: aCost,
    145           };
    146 
    147           const cacheKey = result.toString(2);
    148           if ((!map[cacheKey]) || (newPressure > map[cacheKey])) {
    149             map[cacheKey] = newPressure;
    150             openStates.push({
    151               valves: result,
    152               actors: newActors,
    153               pressure: newPressure,
    154             });
    155           }
    156         }
    157       }
    158 
    159     }
    160 
    161     console.log('');
    162     console.log({ maxPressure });
    163   });