Day 14

prelude

https://adventofcode.com/2024/day/14

import { inputDay, munge } from "./lib/utilities.js"
const test = munge(`p=0,4 v=3,-3
p=6,3 v=-1,-3
p=10,3 v=-1,2
p=2,0 v=2,-1
p=0,0 v=1,3
p=3,0 v=-2,-2
p=7,6 v=-1,-3
p=3,0 v=-1,-2
p=9,3 v=2,3
p=7,3 v=-1,2
p=2,4 v=2,-3
p=9,5 v=-3,-3
`)
display(test)

const input = await inputDay(14)

part 1

Some functions for operations on 2d points

function mul(v, n) {
  return [v[0] * n, v[1] * n]
}

function mod(v1, v2) {
  return [v1[0] % v2[0], v1[1] % v2[1]]
}

function add(v1, v2) {
  return [v1[0] + v2[0], v1[1] + v2[1]]
}

// convert negative values into positive ones according to the rules. We end up
// with negative points because javascript's mod rules are that -3 mod 7 is -3;
// so convert it into (7-3) == 4
function pos(v1, v2) {
  return [v1[0] < 0 ? v2[0] + v1[0] : v1[0], v1[1] < 0 ? v2[1] + v1[1] : v1[1]]
}

With those in place, we can write a function to spin a point n seconds

function spin(p, v1, map, seconds) {
  return pos(mod(add(p, mul(v1, seconds)), map), map)
}

And to find how many are in each quadrant

function quad(ps, map) {
  const quads = [0, 0, 0, 0]
  const xm = Math.floor(map[0] / 2)
  const ym = Math.floor(map[1] / 2)
  for (const [x, y] of ps) {
    if (x < xm && y < ym) quads[0]++
    if (x > xm && y < ym) quads[1]++
    if (x > xm && y > ym) quads[2]++
    if (x < xm && y > ym) quads[3]++
  }
  return quads.reduce((acc, x) => acc * x, 1)
}

Then we can print out our answer

let map = [11, 7]
display(
  quad(
    test.map(([, px, py, , vx, vy]) => spin([px, py], [vx, vy], map, 100)),
    map,
  ),
)

map = [101, 103]
display(
  quad(
    input.map(([, px, py, , vx, vy]) => spin([px, py], [vx, vy], map, 100)),
    map,
  ),
)

part 2

How to find an image in a sea of static? There's a lot of ways, but let's start by checking if the standard deviation of points at a particular index is lower than the others.

Let's define a function to calculate the standard deviation of a set of 2d points

function div(v1, n) {
  return [v1[0] / n, v1[1] / n]
}

function dist(v1, v2) {
  return Math.sqrt(Math.pow(v1[0] - v2[0], 2) + Math.pow(v1[1] - v2[1], 2))
}

function stdev(pts) {
  // Get the mean of the set of points
  const mean = div(
    pts.reduce(([sx, sy], [x, y]) => [sx + x, sy + y], [0, 0]),
    pts.length,
  )

  // calculate the sum of the squared distance of each point from the mean
  const diff = pts.reduce((acc, pt) => acc + Math.pow(dist(pt, mean), 2), 0)

  // return the square root of the sum
  return Math.sqrt(diff / pts.length)
}

Then we can graph the standard deviation of the first 10,000 steps, and see that there's an outlier near step 8,000

const map = [101, 103]
const stdevs = []
for (let i = 0; i < 10_000; i++) {
  const pts = input.map(([, px, py, , vx, vy]) =>
    spin([px, py], [vx, vy], map, i),
  )
  stdevs.push(stdev(pts))
}
display(
  Plot.plot({
    marks: [
      Plot.line(stdevs, {
        stroke: "#4269d0",
        x: Plot.indexOf,
        y: Plot.identity,
      }),
    ],
  }),
)

Which one is that minimum?

function min(a) {
  let min = Number.MAX_VALUE
  let minIdx = -1
  for (let i = 0; i < a.length; i++) {
    if (a[i] < min) {
      minIdx = i
      min = a[i]
    }
  }
  return [min, minIdx]
}
display(min(stdevs))

Let's take a look at step 8053: