Day 9
prelude
https://adventofcode.com/2024/day/9
import { inputDay, maybeNumber, range } from "./lib/utilities.js"
const parser = inp => inp.trim().split("").map(maybeNumber)
const test = parser(`2333133121414131402`)
display(test)
const input = await inputDay(9, { parser })
part 1
split the disk map into a sequence of lengths and free space. I'm representing
files as triples [size, free space, id]
where free space
is the number of
spaces available to the right of the file
function getfiles(inp) {
const files = []
let id = 0
for (let i = 0; i < inp.length; i += 2) {
files.push([inp[i], inp[i + 1] || 0, id++])
}
return files
}
display(s(getfiles(test)))
A function to print a debug string in the same style as the AoC problem statement
function s(files) {
let pos = 0
let s = []
for (const [sz, free, id] of files) {
for (const p of range(pos, pos + sz)) {
s.push(id)
}
if (free) {
for (const p of range(pos + sz, pos + sz + free)) {
s.push(".")
}
}
pos += sz + free
}
return s.map(String).join("")
}
Stuff a single file into the file list piece by piece
function fit(files) {
let [sz, , id] = files.pop()
for (let i = 0; i < files.length; i++) {
const free = files[i][1]
if (free > 0) {
// insert the file into the list
files.splice(i + 1, 0, [Math.min(sz, free), Math.max(free - sz, 0), id])
// the file we're inserting next to no longer has empty space
files[i][1] = 0
// update the size we're looking to place
sz -= free
}
if (sz <= 0) return files
}
files[files.length - 1][1] = 0
files.push([sz, 0, id])
return false
}
Run that function until it completes
// run it until it completes
function fitall(files, debug = false) {
while (fit(files)) {
if (debug) display(s(files))
}
if (debug) display(s(files))
return files
}
fitall(getfiles(test), true)
function checksum(files) {
let pos = 0
let sum = 0n
for (const [sz, free, id] of files) {
for (const p of range(pos, pos + sz)) {
sum += BigInt(p * id)
}
pos += sz + free
}
return sum
}
display(checksum(fitall(getfiles(test))))
display(checksum(fitall(getfiles(input))))
part 2
Took me a minute to figure out an edge case here; see the comments below
function fit2(files, debug = false) {
if (debug) display(s(files))
const maxid = files[files.length - 1][2]
// for each id in descending order
for (let id = maxid; id > 0; id--) {
// find the active file
const activeIdx = files.findIndex(x => x[2] === id)
const [sz, activeFree] = files[activeIdx]
// probe for a place where we can fit the active file. We want to go from
// the start up to the active file
for (let i = 0; i < activeIdx; i++) {
const [, free] = files[i]
if (free >= sz) {
// remove the file we're moving
files.splice(activeIdx, 1)
// add its free space to its left neighbor
files[activeIdx - 1][1] += sz + activeFree
// insert it into the list at its new location. I got stuck here for a
// little bit because I was re-using `free` without realizing that it
// could have changed in the line prior
files.splice(i + 1, 0, [sz, files[i][1] - sz, id])
// the file we're inserting next to no longer has empty space
files[i][1] = 0
if (debug) display(s(files))
break
}
}
}
return files
}
display(checksum(fit2(getfiles(test), true)))
display(checksum(fit2(getfiles(input))))