DEV Community

Discussion on: AoC Day 2: Inventory Management System

Collapse
 
mattmorgis profile image
Matt Morgis • Edited

Node.js

Common async generator. Read the file in chunk by chunk and yield each product ID based on new lines.

// Generator
// "abcdef\n+bababc\n" yields -> abcdef -> bababc
async function* streamToProdctId(stream) {
  let previous = "";
  for await (const chunk of stream) {
    previous += chunk;
    let eolIndex;
    while ((eolIndex = previous.indexOf("\n")) >= 0) {
      // productId excludes the EOL
      const productId = previous.slice(0, eolIndex);
      yield productId;
      previous = previous.slice(eolIndex + 1);
    }
  }
  if (previous.length > 0) {
    yield previous;
  }
}

Part 1 was fun with some ES6 array -> Set -> Map to get the value counts

// ['b', 'a', 'b', 'a', 'b', 'c'] returns Map {3 => 'b', 2 => 'a', 1 => 'c}
const count = array => {
  return new Map(
    [...new Set(array)].map(x => [array.filter(y => y === x).length, x])
  );
};

const checksum = async stream => {
  let idsWith2MatchingLetters = 0;
  let idsWith3MatchingLetters = 0;
  for await (const productId of streamToProductId(stream)) {
    const valueCounts = count([...productId]);
    if (valueCounts.has(2)) {
      idsWith2MatchingLetters++;
    }
    if (valueCounts.has(3)) {
      idsWith3MatchingLetters++;
    }
  }
  return idsWith2MatchingLetters * idsWith3MatchingLetters;
};

Part 2 got interesting. I needed to generate all pairs for every product ID. I made my Hamming Distance function also return the common letters. Then tied it all together by running each pair through the Hamming Distance function and getting the lowest.

const generatePairs = array => {
  return array.reduce(
    (acc, _, i1) => [
      ...acc,
      ...new Array(array.length - 1 - i1)
        .fill(0)
        .map((v, i2) => [array[i1], array[i1 + 1 + i2]])
    ],
    []
  );
};

const hammingDistance = (stringOne, stringTwo) => {
  let distance = 0;
  let commonLetters = "";
  for (let i = 0; i < stringOne.length; i++) {
    if (stringOne[i] !== stringTwo[i]) {
      distance += 1;
    } else {
      commonLetters = commonLetters.concat(stringOne[i]);
    }
  }
  return [distance, commonLetters];
};

const findLowestPairAndRemoveDifferences = pairs => {
  let lowestDistance = Infinity;
  let lowestPair;
  pairs.forEach(pair => {
    const [distance, commonLetters] = hammingDistance(...pair);
    if (distance < lowestDistance) {
      lowestDistance = distance;
      lowestPair = commonLetters;
    }
  });
  return lowestPair;
};

const productIds = async stream => {
  const ids = [];
  for await (const productId of streamToProductId(stream)) {
    ids.push(productId);
  }
  return ids;
};

const findCommon = async stream => {
  return findLowestPairAndRemoveDifferences(
    generatePairs(await productIds(stream))
  );
};

Putting it all together:

const productIdStream = () => {
  return fs.createReadStream(__dirname + "/input.txt", {
    encoding: "utf-8",
    highWaterMark: 256
  });
};

const main = async () => {
  try {
    const part1 = await checksum(productIdStream());
    console.log({part1});
    const part2 = await findCommon(productIdStream());
    console.log({part2});
  } catch (e) {
    console.log(e.message);
    process.exit(-1);
  }
};

Full code here: github.com/MattMorgis/Advent-Of-Co...

Collapse
 
rpalo profile image
Ryan Palo

Making hamming distance return common letters is a slick way to go. I was looking at the duplication between the hamming distance and common letters functionality in my solution and was a little bummed about it, but I couldn't figure out a good way to do it.

I like this!