Day 3 — Rucksack Reorganization
Part 1
I had to do a couple read-throughs of this one, but the solution was actually pretty simple. As usual the first thing we’ll do is split our input into individual “rucksacks”.
const rucksacks = input.split("\n").filter(Boolean);
Once again we’ll reach for good ol’ reduce to take each rucksack, break it into two compartments, and find/prioritize the common item. The first step is to determine the contents of each compartment. Since we know all compartments are split perfectly evenly, we can use slice to split the string in half, and use split to convert it to an array of items.
rucksacks.reduce((total, sack) => {
// Determine the compartments
const firstCompartment = sack.slice(0, sack.length / 2).split("");
const secondCompartment = sack.slice(sack.length / 2, sack.length).split("");
// ...
}, 0);
Next we need to find the common item between the two compartments. A simple Array.find will serve us well here.
return rucksacks.reduce((total, sack) => {
// Determine the compartments
const firstCompartment = sack.slice(0, sack.length / 2).split("");
const secondCompartment = sack.slice(sack.length / 2, sack.length).split("");
// Find the common item
const commonItem = firstCompartment.find((item) =>
secondCompartment.includes(item)
);
// ...
}, 0);
Great! Now we have our item that appears in both compartments, and all we need to do is assign it a priority. We could do this inline, but I think it’s a little easier to read if we break it out into a separate function.
function scoreItem(item: string): number {
if (item.charCodeAt(0) >= 97) {
return item.charCodeAt(0) - 96;
} else {
return item.charCodeAt(0) - 38;
}
}
Here we’re basically abusing the fact that we know 'a'.charCodeAt(0) === 97 and 'A'.charCodeAt(0) === 65. We can use this to determine the priority value of each letter. Now all we do is add the scores together. Here’s the entire reduce function.
rucksacks.reduce((total, sack) => {
// Determine the compartments
const firstCompartment = sack.slice(0, sack.length / 2).split("");
const secondCompartment = sack.slice(sack.length / 2, sack.length).split("");
// Find the common item
const commonItem = firstCompartment.find((item) =>
secondCompartment.includes(item)
);
return total + scoreItem(commonItem);
}, 0);
Voilà! Our answer is 7,878 👍
Part 2
On to part two! The first thing we need to do is chunk the input into groups of 3. There are a tons of different ways of doing array chunking, but I went with a simple combination of a for loop and Array.slice.
function chunkArray(items: string[], chunkSize: number): Array<string[]> {
let chunks: Array<string[]> = [];
for (let i = 0; i < items.length; i += chunkSize) {
chunks = [...chunks, items.slice(i, i + chunkSize)];
}
return chunks;
}
// ...
const rucksacks = input.split("\n").filter(Boolean);
const groups = chunkArray(rucksacks, 3);
Once we have our groups — you guessed it, we’re going to use reduce to find the common item and assign it a priority. We’ll destructure the first and remaining rucksacks of the group to check for the common item.
groups.reduce((total, group) => {
let badge: string = "";
const [firstSack, ...otherSacks] = group.map((sack) => sack.split(""));
// ...
}, 0);
Next we’ll loop over each item in the first rucksack, and check if it exists in the remaining rucksacks. If it does, we’ll assign the badge type to that value and calculate its priority. I’m using a for loop here instead of something like Array.forEach because we need to be able to break out of the loop once we’ve found the common item.
groups.reduce((total, group) => {
let badgeType: string = "";
const [firstSack, ...otherSacks] = group.map((sack) => sack.split(""));
for (let i = 0; i < firstSack.length; i++) {
if (otherSacks.every((sack) => sack.includes(firstSack[i]))) {
badgeType = firstSack[i];
break;
}
}
return total + scoreItem(badgeType);
}, 0);
Great success! Our answer is 2,760 😎