Advent of Code 2015 - Day 12 - JSAbacusFramework.io

On the twelfth day of Advent of Code 2015, Santa’s Accounting-Elves need help balancing the books after a recent order.

Part 1

The supplied input comes in the form of a serialised JSON document, of which we are initially asked to sum up all the numbers present. For this, we are required to traverse through the deserialised document structure and sum up all the numbers.

const sumJsonNumbers = (
  x: any,
  isSkipped = (_: any) => false
): number => {
  if (isSkipped(x)) {
    return 0;
  }

  if (typeof x === 'number') {
    return x;
  }

  if (typeof x === 'object') {
    return Object.values(x)
      .map(y => sumJsonNumbers(y, isSkipped))
      .reduce(sum, 0);
  }

  return 0;
};

With some pre-warning that part two will require us to inspect the provided document in some form, I have opted to provide the ability to optionally include a predicate function which will skip the provided sub-section. As stated in the problem, we can expect all number values to be typed as such, and none are present in string form. Using this function, we can now invoke it with the supplied deserialised JSON document to return the desired answer 🌟.

const part1 = (input: string): number =>
  sumJsonNumbers(JSON.parse(input));

Part 2

As alluded to in part one, we are now required to, instead of simply summing up all numbers present, apply some predicate logic. We are now asked to ignore any object (and all of its children) that has any property with the value “red”.

const part2 = (input: string): number =>
  sumJsonNumbers(
    JSON.parse(input),
    x =>
      typeof x === 'object' &&
      !Array.isArray(x) &&
      Object.values(x).includes('red')
  );

The predicate function itself checks to ensure that the current document value is not an Array (as Arrays are represented as Objects in JavaScript) and then attempts to see if any of the Object’s values include the value “red”. We can then invoke the sumJsonNumbers function again, returning the required answer for part two 🌟.

Alternative Solution

Alternatively, we could tackle this problem in a more simplified manner. Instead of deserialising the JSON document and recursing over its contents, we could just employ a Regular Expression to extract all numbers present.

const part1 = (input: string): number =>
  (input.match(/(-?\d)+/g) || []).reduce(
    (sum, val) => sum + toInt(val),
    0
  );

For solving part two, we could then harness a lesser-known second argument to JSON.parse and ensure that when attempting to deserialise the input, we revive its contents only if it matches the given predicate.

const part2 = (input: string): number =>
  part1(
    JSON.stringify(
      JSON.parse(input, (_, value) =>
        typeof value === 'object' &&
        !Array.isArray(value) &&
        Object.values(value).includes('red')
          ? ''
          : value
      )
    )
  );

We can then subsequently re-serialise this reduced JSON document and supply it to our part one solution to determine the answer.