DEV Community

Discussion on: Advent of Code 2019 Solution Megathread - Day 14: Space Stoichiometry

Collapse
 
maxart2501 profile image
Massimo Artizzu

Oooh another nice problem :)

First of all, parsing. Fairly simple, you just have to create a map. Note that there's only one reaction to produce a specific chemical.
But the produced amount may vary, so we have to track that information. For some silly reason, I used a JavaScript Symbol to store that in the map 😅. I basically never used Symbols in my job, but here I am practicing with them:

const quantityKey = Symbol();
const reactions = input.trim().split('\n').reduce((map, line) => {
  const [ ingredientList, result ] = line.split(' => ');
  const [ quantity, chemical ] = result.split(' ');
  map[chemical] = ingredientList.split(', ').reduce((ingredientMap, combo) => {
    const [ qty, chem ] = combo.split(' ');
    ingredientMap[chem] = +qty;
    return ingredientMap;
  }, { [quantityKey]: +quantity });
  return map;
}, {});

The basic idea here is, of course, that you have to start with the reaction needed for FUEL (there's only one) and create a shopping list of the chemicals you need (and how many of them). But while doing so, you also have to keep track of the chemicals you end up not using for the current reaction, because they may come in handy for the next reactions.

I'm wrapping everything in a function so it can be used in the second part:

function getNeededOre(fuel) {
  let neededChemicals = { FUEL: fuel };
  const reserves = {};
  while (Object.keys(neededChemicals).length !== 1 || !('ORE' in neededChemicals)) {
    const newNeededList = {};
    for (const [ chemical, quantity ] of Object.entries(neededChemicals)) {
      if (chemical === 'ORE') {
        newNeededList.ORE = (newNeededList.ORE || 0) + quantity;
        continue;
      }
      const reaction = reactions[chemical];
      const reactionQuantity = reaction[quantityKey];
      const reactionCount = Math.ceil((quantity - (reserves[chemical] || 0)) / reactionQuantity);
      for (const [ ingredient, amount ] of Object.entries(reaction)) {
        newNeededList[ingredient] = (newNeededList[ingredient] || 0) + reactionCount * amount;
      }
      reserves[chemical] = (reserves[chemical] || 0) + reactionCount * reactionQuantity - quantity;
    }
    neededChemicals = newNeededList;
  }
  return neededChemicals.ORE;
}

const orePer1Fuel = getNeededOre(1);

console.log('Part 1:', orePer1Fuel);

For the second part, we have to remind that after producing 1 FUEL, we still have some useful chemicals left. So, should we produce one FUEL at a time, always keeping track of the reserves? I had a better idea, and it involves estimations. Turns out it reaches the correct answer pretty fast (right at the second iteration for me!):

const TOTAL_ORE = 1e12;
let estimate;
let newEstimate = Math.floor(TOTAL_ORE / orePer1Fuel);
do {
  estimate = newEstimate;
  const neededEstimate = getNeededOre(estimate);
  newEstimate = Math.floor(estimate * TOTAL_ORE / neededEstimate);
} while (newEstimate > estimate)
console.log('Part 2:', estimate);

Text, input and solution at my repo 👋