Advent of Code 2024: Day 1
aoc 2024 typescript bun
Advent of Code 2024 Day 1
Updating my astro site
First thing I did was get astro updated an up and running (which runs my personal website). It took a bit, but following their public docs and some googling did the trick. Lucky for me I was on Astro 4, and upgrading to 5 was pretty seamless
Project Setup
Installing Bun
Iām deciding to use bun, and TypeScript this year, so learning something new and something familiar. I may go back and do this again in something like Go or Rust, but as a father with limited free time, starting with TypeScript.
First thing is first, installing Bun. Going to their public docs, itās as easy as (on my Ubuntu 24.04 WSL2 Instance)
curl -fsSL https://bun.sh/install | bash
Well, not quite, had to install āunzipā first (give me a break, I work for Microsoft, this WSL instance has barely been touched, I live in Powershell and Windows)
Setting up the project
I create a new folder in my advent of code github repo, and I bet dollars to donuts the init command is
bun init
Which works great. It creates a package.json, an index.ts, a README and a few others.
Iām dying to get going on this, so instead of thinking about project architecture up front, I figure Iāll refine it as I go. So, weāll do something simple to start. For directory structure, something like:
index.ts // runs all the days, maybe caches the answers
utils.ts // I wrote a bunch of duplicative code last time, let's actually do a little DRY
dayone
index.ts
data.txt // that way the import is always the same
daytwo
index.ts
data.txt
...
dayn
...
Easy enough, Iāll add that to utils later, so letās with dayone/index.ts start reading the problem
The Problem
Being real, I skimmed the narrative, and got to work.
Reading the problem, seems simple enough, there are two columns of text that need matched up from smallest to largest, then the sum of their difference. Going to assume the lists will be the same size, so I first come up with my solution to parsing the data.
The unknown (parsing text)
So the first thing I need is something that can parse text, I see if bun has built in file system APIs (asking copilot)
Phil: Does bun have built in file system APIs?
Copilot: Yes, Bun has built-in file system APIs. You can use the fs module in Bun to interact with the file system. Here is an example of how to use it:
import { readFile, writeFile } from 'bun:fs';
// Reading a file
const data = await readFile('example.txt', 'utf8');
console.log(data);
// Writing to a file
await writeFile('example.txt', 'Hello, Bun!');
Okay not too bad, but pasting that in, I get an error
Cannot find module 'bun:fs' or its corresponding type declarations.ts(2307)
Maybe this is like node? I try a
bun install @types/bun
no luckā¦
Turns out copilot hallucinated. Here is from the bun docs on how to read in a file
const foo = Bun.file("foo.txt");
await foo.text(); // contents as a string
await foo.stream(); // contents as ReadableStream
await foo.arrayBuffer(); // contents as ArrayBuffer
await foo.bytes(); // contents as Uint8Array
In this case, I canāt seem to trust copilot, maybe the bun api used to use ābun:fsā, not sure, but itās not right now. Iāll lean towards the docs in the future
Either way, so constructing this
const data = Bun.file("data.txt");
const text = await data.text();
console.log(text);
I get, for their example input
$ bun index.ts
3 4
4 3
2 5
1 3
3 9
3 3
Awesome, the problem is comparatively cake now, I need to parse out the two columns, sort them, and sum their difference.
Part One
Iāll split the text on the newline character, then split the line on the separator (three spaces), convert each side into an integer
const lines = text.split("\n");
for (const line of lines) {
const [leftNumber, rightNumber] = line
.split(" ")
.map((numberString) => +numberString) as [number, number];
left.push(leftNumber);
right.push(rightNumber);
}
Next, I need to sort the arrays, Iāll do so in place
left.sort();
right.sort();
Then, finally, summing their differences. I could do this a variety of ways, but a plain old for loop makes the most sense to my brain
let sum = 0;
for (let i = 0; i < left.length; i++) {
const leftNumber = left[i];
const rightNumber = right[i];
const difference = Math.abs(leftNumber - rightNumber);
sum += difference;
}
console.log(sum);
Making the final solution
const data = Bun.file("data.txt");
const text = await data.text();
const left: number[] = [];
const right: number[] = [];
const lines = text.split("\n");
for (const line of lines) {
const [leftNumber, rightNumber] = line
.split(" ")
.map((numberString) => +numberString) as [number, number];
left.push(leftNumber);
right.push(rightNumber);
}
left.sort();
right.sort();
let sum = 0;
for (let i = 0; i < left.length; i++) {
const leftNumber = left[i];
const rightNumber = right[i];
const difference = Math.abs(leftNumber - rightNumber);
sum += difference;
}
console.log(sum);
Okay, running it, I get
$ bun index.ts
11
Okay, thatās the same as the reported answer, now to try on my puzzle input, crossing my fingers for no corner cases or gotchaās I didnāt think of
$ bun index.ts
765748
My answer happened to be a little less than a million, 765748, and that was correct!
On to part two, letās see if my solution can be extended
Part Two
Okay, so reading through the fluff, I need to figure out the frequency that numbers in the left list appear in the right list, multiply the frequency by the number, then sum that. I can reuse some of what I had so far, cleaning it up a bit, I separate them out into part one and part two functions, as well as a function that gets me the lines and text.
const getProblemText = async () => {
const data = Bun.file("data.txt");
const text = await data.text();
return text;
};
const getProblemLines = (text: string) => text.split("\n");
const partOne = (lines: string[]) => {
const left: number[] = [];
const right: number[] = [];
for (const line of lines) {
const [leftNumber, rightNumber] = line
.split(" ")
.map((numberString) => +numberString) as [number, number];
left.push(leftNumber);
right.push(rightNumber);
}
left.sort();
right.sort();
let sum = 0;
for (let i = 0; i < left.length; i++) {
const leftNumber = left[i];
const rightNumber = right[i];
const difference = Math.abs(leftNumber - rightNumber);
sum += difference;
}
return sum;
};
const partTwo = (lines: string[]) => {
// implement me!
return 0;
};
const text = await getProblemText();
const lines = getProblemLines(text);
console.log("part one:", partOne(lines));
console.log("part two:", partTwo(lines));
Working on part two, I could start with two lists, and manually iterate through to get a count, but Iād like to do some pre-work and build up a map of frequencies in advance. Something like:
const rightNumberToFrequencyLookup = new Map<number, number>();
const left: number[] = [];
for (const line of lines) {
const [leftNumber, rightNumber] = line
.split(" ")
.map((numberString) => +numberString) as [number, number];
left.push(leftNumber);
if (!rightNumberToFrequencyLookup.has(rightNumber)) {
rightNumberToFrequencyLookup.set(rightNumber, 1);
} else {
const current = rightNumberToFrequencyLookup.get(rightNumber)!;
rightNumberToFrequencyLookup.set(rightNumber, current + 1);
}
}
Now itās as simple as taking the numbers in the left, iterating one by one, looking them up in the map, and performing the calculation. Something like
let sum = 0;
for (const item of left) {
const frequencyInRight = rightNumberToFrequencyLookup.get(item) ?? 0;
sum += frequencyInRight * item;
}
return sum;
Making my whole part two solution:
const partTwo = (lines: string[]) => {
const rightNumberToFrequencyLookup = new Map<number, number>();
const left: number[] = [];
for (const line of lines) {
const [leftNumber, rightNumber] = line
.split(" ")
.map((numberString) => +numberString) as [number, number];
left.push(leftNumber);
if (!rightNumberToFrequencyLookup.has(rightNumber)) {
rightNumberToFrequencyLookup.set(rightNumber, 1);
} else {
const current = rightNumberToFrequencyLookup.get(rightNumber)!;
rightNumberToFrequencyLookup.set(rightNumber, current + 1);
}
}
let sum = 0;
for (const item of left) {
const frequencyInRight = rightNumberToFrequencyLookup.get(item) ?? 0;
sum += frequencyInRight * item;
}
return sum;
};
Running it on the example, I get 31, which is the expected sum
part one: 11
part two: 31
Trying on my problem input, I get 27732508, which is correct
Done and dusted š š