lewisdale.dev/src/blog/posts/2023/12/advent-of-code-2023-day-three.md
2023-12-26 14:35:09 +00:00

4.7 KiB

title date slug tags
Advent of Code 2023: Day Three 2023-12-03T12:23:59 advent-of-code-2023-day-three
advent-of-code-2023

As before, this post contains spoilers. You can follow all of the Advent of Code posts using the Advent of Code tag, and the code is available on Git.

Thanks Day Three, I hate it.

Part One

For part one, we had to take a schematic of an engine, with symbols for components, and numbers for "part numbers", and then find all of the part numbers adjacent to a component, and sum them together.

I really struggled to get the right data structure for this while still being able to look it up properly. In the end, I matched every integer, stuck it in a 2D array, at each index that int would occupy (e.g. '245' starting at position 3, line 3 would be in indexes [3][3], [3][4], and [3][5].

Then I found each symbol, checked the adjacent spots for numbers, removed the duplicates, and summed them. The code is horrible.

export class Engine {
    constructor(private readonly parts: number[][], private readonly schematic: string[][]) {
    }

    public static create(input: string): Engine {
        const lines = input.split('\n').filter(Boolean);
        const partRegex = /\d+/g;
        const symbolRegex = /[^a-zA-Z\d\.]/g;

        const parts : number[][] = new Array(lines.length).fill(0).map(() => new Array(lines[0].length));
        const symbols: string[][] = new Array(lines.length).fill(0).map(() => new Array(lines[0].length));

        lines.forEach((line, lineNumber) => {
            matchAllAndThen(line, partRegex, (match, index) => {
                const parsedNumber = parseInt(match, 10);
                for (let i = index; i < index + match.length; i++) {
                    parts[lineNumber][i] = parsedNumber;
                }
            });
        });

        lines.forEach((line, lineNumber) => {
            matchAllAndThen(line, symbolRegex, (match, index) => symbols[lineNumber][index] = match);
        });

        return new Engine(parts, symbols);
    }

    public sumPartNumbers(): number {
        const partsList = this.schematic.flatMap((row, rowIndex) =>
            row.map((symbol, index) => {
                if (!symbol) return symbol;

                const partIndex = [
                    [rowIndex - 1, index - 1],
                    [rowIndex - 1, index],
                    [rowIndex - 1, index + 1],
                    [rowIndex, index - 1],
                    [rowIndex, index + 1],
                    [rowIndex + 1, index - 1],
                    [rowIndex + 1, index],
                    [rowIndex + 1, index + 1]
                ];
                return Array.from(new Set(partIndex.filter(([rowNum, col]) => rowNum >= 0 && rowNum <= this.schematic.length && index >= 0 && index <= row.length)
                    .map(([rowNum, column]) => {
                        return this.parts[rowNum][column]
                    }).filter(Boolean)))
                    .reduce((total, val) => total + val,0);
            })
        ) as number[];
        return partsList.reduce((total, partNumber) => total + partNumber, 0);
    }

But whatever, it works.

Part Two

This was easier - now we just need to find * symbols with exactly two adjacent numbers. I added a flag to my create function that only matched * symbols, and then filtered the list of part numbers to ones where length === 2. Finally, I reduced the list down and summed the "ratios":

public gearRatioSums(): number {
        return  this.schematic.flatMap((row, rowIndex) =>
            row
                .map((symbol, index) => {
                const partIndex = [
                    [rowIndex - 1, index - 1],
                    [rowIndex - 1, index],
                    [rowIndex - 1, index + 1],
                    [rowIndex, index - 1],
                    [rowIndex, index + 1],
                    [rowIndex + 1, index - 1],
                    [rowIndex + 1, index],
                    [rowIndex + 1, index + 1]
                ];
                return Array.from(new Set(partIndex.filter(([rowNum, col]) => rowNum >= 0 && rowNum <= this.schematic.length && index >= 0 && index <= row.length)
                    .map(([rowNum, column]) => {
                        return this.parts[rowNum][column]
                    }).filter(Boolean)))
            })
        ).filter(list => list.length === 2)
            .reduce((total, [a, b]) => {
                return total + (a * b)
            }, 0);
    }

Today took me way longer than I'd have expected. Feels like this year is surprisingly hard 😅