AoC 2025 - Day 3

Day 3 of AoC 2025. Following up on yesterday, today I'm gonna try to solve the question using typescript, but in a Jupyter notebook, writing along while programming. Today's input looks like this:

import fs from "node:fs"
const input = fs.readFileSync("input.txt", "utf-8")

console.log(input.split('\n').slice(0,3))
[
  "2215452689925244273244333436189317446384838478525478824435233342352236255624326767355438753493222423",
  "1222232323222232132222323221226222225212221213232122232311152232223212123622111212223162322221323211",
  "3786645737446363554463656544667372864465545434545435744345766553343446943531537627746253556233634463"
]

// sample input
const sample = `\
987654321111111
811111111111119
234234234234278
818181911112111\
`
console.log(sample)
987654321111111
811111111111119
234234234234278
818181911112111

The first part asks as to choose in each row two digits that taken together form the largest number in that row. The two digits must maintain their left-to-right order. For example: - in row 1: 987654321111111 forms 98 - in row 2: 811111111111119 forms 89 - in row 3: 234234234234278 forms 78 - in row 4: 818181911112111 forms 92

Then we sum them all and get

98+89+78+92
357

which is the final answer. Some insights: - the left digit dominates the size comparison - the left digit will never be the last one, because there won't be room for the right digit - if the same digit appears twice, it's always better to pick the left most one (only increase the right digit candidates this way)

Okay, so the algorithm will be as follows: - left_digit = max(row[:-1]) - right_digit = max(row[ind_left_digit:])

// the name jolt is a ref to the AoC lore in the question
function maxJolt(nums: number[]) {
  const lcand = nums.slice(0, -1)
  const ldigit = Math.max(...lcand)
  const lind = lcand.indexOf(ldigit)
  const rdigit = Math.max(...nums.slice(lind+1, nums.length))
  return ldigit * 10 + rdigit
}

function part1(input: string, debug=false) {
  let sum = 0;
  for (const r of input.split("\n")) {
    const res = maxJolt(r.split('').map(n => parseInt(n)))
    sum += res
    if (debug) {
      console.log(r)
      console.log(res)
      console.log(sum)
    }
  }
  return sum
}
console.log(part1(sample, true))
987654321111111
98
98
811111111111119
89
187
234234234234278
78
265
818181911112111
92
357
357

Looks good, let's try the full input

console.log(part1(input))
17346

And we were right! let's move on to part 2. This time I leave more intermediate cells so you can see sum of the incremental process.

Now the twist is that instead of 2 digits we need to choose 12 digits the form the largest number! I think the same idea as before can be extended to the general case: (i+1)_digit = max(row[i_digit: -(12-i)]). Might be easier to just build the algorithm to be generic to the number of digits, then we can verify if we reproduce the same results.

const r = "987654321111111"
const nums = r.split('').map(n => parseInt(n))
const digits = []
const n_digits = 2
let last_ind = -1
let i = n_digits - 1
nums
[
  9, 8, 7, 6, 5, 4,
  3, 2, 1, 1, 1, 1,
  1, 1, 1
]

We'll add to digits one by one

let slice = nums.slice(last_ind + 1, nums.length-i)
slice
[
  9, 8, 7, 6, 5, 4,
  3, 2, 1, 1, 1, 1,
  1, 1
]
let digit = Math.max(...slice)
digits.push(digit)
digit
9
last_ind = nums.slice(last_ind+1, nums.length).indexOf(digit)
last_ind
0
i = i - 1
slice = nums.slice(last_ind + 1, nums.length-i)
slice
[
  8, 7, 6, 5, 4, 3,
  2, 1, 1, 1, 1, 1,
  1, 1
]
digit = Math.max(...slice)
digits.push(digit)
digit
8
digits
[ 9, 8 ]

Okay, so we kinda get how to body should work, let's merge it all, and try the 12 digits. Should get 987654321111

const r = "987654321111111"
const nums = r.split('').map(n => parseInt(n))
const digits = []
const n_digits = 12
let last_ind = -1
// let i = n_digits - 1
for (let i = n_digits - 1; i >=0; i--) {
  const slice = nums.slice(last_ind + 1, nums.length-i)  
  const digit = Math.max(...slice)
  last_ind = last_ind + slice.indexOf(digit) + 1
  digits.push(digit)  
}
digits
[
  9, 8, 7, 6, 5,
  4, 3, 2, 1, 1,
  1, 1
]

To turn it into a number we can use reduce

digits.reduce((s, x) => s*10 + x, 0)
987654321111

Looks good, let's put it in a function and try the rest of the sample

function maxJolt2(nums, n_digits) {
  const digits = []
  let last_ind = -1
  for (let i = n_digits - 1; i >=0; i--) {
    const slice = nums.slice(last_ind + 1, nums.length-i)  
    const digit = Math.max(...slice)
    last_ind = last_ind + slice.indexOf(digit) + 1
    digits.push(digit)  
  }
  return digits.reduce((s, x) => s*10 + x, 0)
}
let sum = 0
sample.split("\n").forEach(r => {
  const nums = r.split('').map(n => parseInt(n))
  const res = maxJolt2(nums, 12)
  console.log(res)
  sum += res
})
console.log(sum)
987654321111
811111111119
434234234278
888911112111
3121910778619

Looks correct. Finally, let's put that in a function and finish

function part2(input: string) {
  let sum = 0
  input.split("\n").forEach(r => {
    const nums = r.split('').map(n => parseInt(n))
    const res = maxJolt2(nums, 12)
    // console.log(res)
    sum += res
  })
  return sum
}

part2(input)
172981362045136

Success!

As a bonus, I want to see if we can rewrite maxJolt2 in a functional way. I'm thinking recursion

function rMaxJolt2(nums, n_digits) {
  if (n_digits == 1) return Math.max(...nums);
  const digit = Math.max(...nums.slice(0, -n_digits + 1))
  return digit * 10**(n_digits - 1) + rMaxJolt2(nums.slice(nums.indexOf(digit) + 1, nums.length), n_digits - 1)
}

rMaxJolt2("818181911112111".split("").map(n => parseInt(n)), 12)
888911112111

social