DEV Community

Discussion on: AoC Day 1: Chronal Calibration

Collapse
 
mattmorgis profile image
Matt Morgis • Edited

Node.js

First, I created an async generator that read the input file stream chunk by chunk and yield each number line by line.

async function* streamToFrequencies(stream) {
  let previous = "";
  for await (const chunk of stream) {
    previous += chunk;
    let eolIndex;
    while ((eolIndex = previous.indexOf("\n")) >= 0) {
      // exclude the EOL
      const number = previous.slice(0, eolIndex);
      yield parseInt(number);
      previous = previous.slice(eolIndex + 1);
    }
  }
  if (previous.length > 0) {
    yield parseInt(previous);
  }
}

Then part 1 was pretty straight forward:

const addFrequencies = async frequencies => {
  let sum = 0;
  for await (const frequency of frequencies) {
    sum += frequency;
  }
  return sum;
};

const sum = stream => {
  return addFrequencies(streamToFrequencies(stream));
};

This approach made part 2 ugly because I had to find a way to re-open the file stream and loop back through the inputs. Using a set over an array really helped with performance.

const calibrate = async stream => {
  let currentFrequency = 0;
  const frequenciesFound = new Set([0]);

  while (true) {
    // clone stream and put in cold storage
    // in case we need to re-read inputs.
    let frozenStream = clone(stream);

    for await (const frequency of streamToFrequencies(stream)) {
      currentFrequency += frequency;
      if (frequenciesFound.has(currentFrequency)) {
        return currentFrequency;
      }

      frequenciesFound.add(currentFrequency);
    }
    stream = frozenStream;
  }
};

Putting it all together:

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

const main = async () => {
  const frequencySum = await sum(frequencyStream());
  console.log({frequencySum})
  const frequencyCalibration = await calibrate(frequencyStream());
  console.log({frequencyCalibration});
};

main();

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

Collapse
 
themindfuldev profile image
Tiago Romero Garcia • Edited

I also did mine in JS but I decided to use the readline interface to read each line individually and spend less memory by not loading the entire file in the memory at once.

I haven't trying using for await (... of ...) with the readline interface. Maybe I'll try that next. If anyone would like to try it please post it here.

Here are my solutions:

My solution in JavaScript / Node 11, using the readline interface:

readLines.js

const fs = require('fs');
const readline = require('readline');

const readLines = (file, onLine) => {
    const reader = readline.createInterface({
        input: fs.createReadStream(file),
        crlfDelay: Infinity
    });

    reader.on('line', onLine);

    return new Promise(resolve => reader.on('close', resolve));
};

const readFile = async file => {
    const lines = [];
    await readLines(file, line => lines.push(line));  
    return lines;
}

module.exports = {
    readLines,
    readFile
};

01a.js

const { readFile } = require('./readLines');

(async () => {
    const lines = await readFile('01-input.txt');

    const frequency = lines.reduce((frequency, line) => frequency + Number(line), 0);

    console.log(`The final frequency is ${frequency}`);
})();

01b.js

const { readFile } = require('./readLines');

(async () => {
    const lines = await readFile('01-input.txt');

    const frequencySet = new Set();

    let frequency = 0;
    let didAFrequencyReachTwice = false;

    while (!didAFrequencyReachTwice) {
        for (let line of lines) {
            frequency += Number(line);
            if (frequencySet.has(frequency)) {
                didAFrequencyReachTwice = true;
                break;
            }
            else {
                frequencySet.add(frequency);
            }
        }
    }

    console.log(`The first frequency reached twice is ${frequency}`);
})();
Collapse
 
mattmorgis profile image
Matt Morgis

Readline doesn't work with async iterators and for await yet, but it just landed in 11.x staging.

Once it is released, my streamToFrequencies generator won't be needed.

Also, createReadStream only reads the file in 256 byte chunks at a time (or whatever you set the highwatermark to be, it does not read the entire file into memory. readFile would, however.

To read more about async iterators and generators and the for await syntax, check out 2ality.com/2018/04/async-iter-node...

Thread Thread
 
themindfuldev profile image
Tiago Romero Garcia

Thanks a bunch @mattmorgis !