Hung-Yi's LogoHung-Yi’s Journal

Advent of Code 2022: Day 4

A TypeScript solution for the Advent of Code 2022, Day 4 puzzle: it looks like set intersection, but actually it's much easier than that.

Today’s puzzle asks us to check if two ranges of numbers overlap or not. We have to check for complete overlaps and partial overlaps.

Even though the puzzle makes it sound like you want to use the set intersection techniques from yesterday, it’s actually much simpler than that. We just need a couple of functions to check the high and low points of the ranges with basic inequality operators (e.g. <= and >=) to find the answer.

The puzzle input data looks something like this.

2-4,6-8
2-3,4-5
5-7,7-9
2-8,3-7
6-6,4-6
2-6,4-8

First we split the puzzle data into Pairs of Range structures that have hi and lo values. Curiously, data parsing seems to be the most challenging part of today’s puzzle.

interface Range { lo: number; hi: number; }
interface Pair { left: Range; right: Range; }

const pairs: Pair[] = puzzleInput
  .split('\n')
  .map(line => line
    .split(',')
    .map(member => member.split('-').map(x => parseInt(x)))
    .map(([lo, hi]) => ({ lo, hi } as Range))
  )
  .map(([left, right]) => ({ left, right } as Pair));

Define a function within that checks if a number is within a Range.

const within = (x: number, range: Range) =>
  range.lo <= x && x <= range.hi;

Define a function isRedundant that checks a Pair of ranges to see if one of them is redundant (i.e. one entirely contains the other). To check, we see if both of the low/high values of range left are within range right, or vice versa.

const isRedundant = ({ left, right }: Pair) =>
  (within(left.lo, right) && within(left.hi, right))
  || (within(right.lo, left) && within(right.hi, left));

Define a function isOverlap that checks if a Pair’s ranges overlap at all. To check, we see if either of the low/high values of range left are within range right, or vice versa.

const isOverlap = ({ left, right }: Pair) =>
  (within(left.lo, right) || within(left.hi, right))
  || (within(right.lo, left) || within(right.hi, left));

For part 1, filter the pairs to where ranges are redundant, then interrogate length to get a count.

const part1 = pairs.filter(isRedundant).length;

Part 2 is the very similar to part 1, except we filter for overlapping pairs instead.

const part2 = pairs.filter(isOverlap).length;

Final Solution

interface Range { lo: number; hi: number; }
interface Pair { left: Range; right: Range; }

const pairs: Pair[] = puzzleInput
  .split('\n')
  .map(line => line
    .split(',')
    .map(member => member.split('-').map(x => parseInt(x)))
    .map(([lo, hi]) => ({ lo, hi } as Range))
  )
  .map(([left, right]) => ({ left, right } as Pair));

const within = (x: number, range: Range) =>
  range.lo <= x && x <= range.hi;

const isRedundant = ({ left, right }: Pair) =>
  (within(left.lo, right) && within(left.hi, right))
  || (within(right.lo, left) && within(right.hi, left));

const isOverlap = ({ left, right }: Pair) =>
  (within(left.lo, right) || within(left.hi, right))
  || (within(right.lo, left) || within(right.hi, left));

const part1 = pairs.filter(isRedundant).length;

const part2 = pairs.filter(isOverlap).length;

console.log("Part 1:", part1);
console.log("Part 2:", part2);
Part 1: 560
Part 2: 839