• 0 Posts
  • 144 Comments
Joined 2 years ago
cake
Cake day: June 20th, 2023

help-circle













  • TypeScript

    Maaaannnn. Today’s solution was really something. I actually got so confused initially that I unknowingly wrote the algorithm for part 2 before I even finished part 1! As an upside, however, I did expand my own Advent of Code standard library ;)

    Solution
    import { AdventOfCodeSolutionFunction } from "./solutions";
    import { Grid } from "./utils/grids";
    import { LinkedPoint } from "./utils/structures/linkedPoint";
    import { makeGridFromMultilineString, SumArray } from "./utils/utils";
    
    class TrailPoint extends LinkedPoint<number, TrailPoint> {
        constructor(x: number, y: number, item: number, grid: Grid<TrailPoint>) {
            super(x, y, item, grid);
        }
    
        lookAroundValid(): Array<TrailPoint> {
            return this.lookAround().filter(v => v.item == this.item + 1);
        }
    
        findAllValidPeaks(): Array<TrailPoint> {
            if (this.item == 9)
                return [this];
    
            // filter for distinct references (this theoretically saves time)
            return [...(new Set(this.lookAroundValid().flatMap(v => v.findAllValidPeaks())))];
        }
    
        findAllValidPeaksWithReps(): Array<TrailPoint> {
            if (this.item == 9)
                return [this];
    
            // don't filter
            return this.lookAroundValid().flatMap(v => v.findAllValidPeaksWithReps());
        }
    }
    
    export const solution_10: AdventOfCodeSolutionFunction = (input) => {
        const map: Grid<TrailPoint> =
            makeGridFromMultilineString(input)
                .map((row) => row.map((item) => item != "." ? Number(item) : -1))
                .map((row, y) => row.map((item, x) => new TrailPoint(x, y, item, undefined!)));
    
        map.flat().forEach((v) => v.grid = map); // promise is a promise
    
        const startNodes: Array<TrailPoint> = map.flat().filter(v => v.item == 0);
    
        const part_1 = SumArray(startNodes.map(v => v.findAllValidPeaks().length));
        const part_2 = SumArray(startNodes.map(v => v.findAllValidPeaksWithReps().length));
    
        return {
            part_1, // 557
            part_2, // 1062
        }
    }
    

    Full code here.


  • TypeScript

    Actually kinda proud of my solution considering how hectic today has been! I actually didn’t spend too much time on this solution too :) Runs in ~0.5s.

    Solution
    import { AdventOfCodeSolutionFunction } from "./solutions";
    import { MakeEmptyGenericArray } from "./utils/utils";
    
    const pretty_print = (disk: Array<number>) => disk.reduce<string>((prev, curr) => prev + (curr == -1 ? "." : curr), "");
    const checksum = (disk: Array<number>) => disk.reduce<number>((prev, curr, index) => prev + (curr == -1 ? 0 : curr * index), 0);
    
    const findSlice = (disk: Array<number>, id: number, startFrom?: number) => {
        const sectionStart = disk.indexOf(id, startFrom);
    
        if (sectionStart == -1)
            return [-1, -1];
    
        let sectionEnd = sectionStart;
    
        while (disk.length > ++sectionEnd && disk[sectionEnd] == id);
    
        return [sectionStart, sectionEnd];
    }
    
    export const solution_9: AdventOfCodeSolutionFunction = (input) => {
        let isFile = false;
        let id = 0;
    
        // make the disk
        const disk = input.split("").flatMap((v) => {
            isFile = !isFile;
            const count = Number(v);
    
            if (isFile) {
                id++;
                return MakeEmptyGenericArray(count, () => id - 1);
            }
    
            return MakeEmptyGenericArray(count, () => -1);
        });
    
        // make a copy of the disk
        const fragmentedDisk = [...disk];
    
        // start moving elements on the disk
        let start = 0
        let end = fragmentedDisk.length - 1;
        while (start < end) {
            if (fragmentedDisk[start] != -1) {
                start++;
                continue;
            }
    
            if (fragmentedDisk[end] == -1) {
                end--;
                continue;
            }
    
            // swap the values
            fragmentedDisk[start] = fragmentedDisk[end]
            fragmentedDisk[end] = -1;
    
            start++;
            end--;
        }
    
    
        main: while (id-- > 0) {
            // find the section that has the file
            const [sectionStart, sectionEnd] = findSlice(disk, id); // this will never return -1
            const sectionLength = sectionEnd - sectionStart;
    
            // find any section that can fit the file
            let freeStart;
            let freeEnd = 0;
            do {
                [freeStart, freeEnd] = findSlice(disk, -1, freeEnd);
    
                // can't find any free spaces or too far right
                if (freeStart == -1 || freeStart > sectionStart)
                    continue main;
    
            } while (freeEnd - freeStart < sectionLength);
    
            // switch places
            let i = 0;
            while(sectionStart + i < sectionEnd) {
                disk[freeStart + i] = id;
                disk[sectionStart + i++] = -1;
            }
        }
    
    
        // calculate the checksums
        return {
            part_1: checksum(fragmentedDisk),
            part_2: checksum(disk),
        }
    }
    
    


  • TypeScript

    I was a little confuzzled with this one, but I managed to get it. :) Happy to know that I managed to reuse more of my code from previous days. I should write something to handle Vectors. It was sad to write my own basic, non-reusable thing.

    Solution
    import { AdventOfCodeSolutionFunction } from "./solutions";
    
    // imported code:
    export const check_coords = (grid: Array<Array<any>>, x: number, y: number) => {
        return y >= grid.length ||
            y < 0 ||
            x >= grid[y].length ||
            x < 0
    }
    
    export const makeGridFromMultilineString =
        (input: string) => input.split("\n").map(st => st.trim()).map(v => v.split(""));
    
    export const MakeEmptyGenericArray = <T>(length: number, fn: (index: number) => T) => {
        const newArray = [];
        let i = 0;
        while (i++ < length)
            newArray.push(fn(i));
    
        return newArray;
    }
    
    export const MakeEmptyArray = (length: number) => MakeEmptyGenericArray(length, () => 0);
    export const MakeEmpty2DArray = (x: number, y: number) => MakeEmptyArray(y).map(() => MakeEmptyArray(x));
    
    // solution code
    type v2 = [x: number, y: number];
    
    const Sub = (x1: number, y1: number, x2: number, y2: number): v2 => {
        return [x1 - x2, y1 - y2];
    }
    
    const Add = (x1: number, y1: number, x2: number, y2: number): v2 => {
        return [x1 + x2, y1 + y2];
    }
    
    export const solution_8: AdventOfCodeSolutionFunction = (input) => {
        const grid = makeGridFromMultilineString(input);
        const nodes = new Map<string, Array<v2>>();
        const nodeKinds: Array<string> = [];
        const singleAntinodeLocations = MakeEmpty2DArray(grid.length, grid[0].length);
        const resonantAntinodeLocations = MakeEmpty2DArray(grid.length, grid[0].length);
    
        // find all the nodes
        grid.forEach((row, y) => row.forEach((item, x) => {
            if (item == ".")
                return;
    
            if (nodes.has(item))
                nodes.get(item)!.push([x, y]);
    
            else {
                nodes.set(item, [[x, y]]);
                nodeKinds.push(item);
            }
        }));
    
        nodeKinds.forEach((nodeKind) => {
            const nodesOfKind = nodes.get(nodeKind)!;
            for (let bunn = 0; bunn < nodesOfKind.length; bunn++) {
                const first = nodesOfKind[bunn];
                for (let tort = bunn + 1; tort < nodesOfKind.length; tort++) {
                    // find antinode
                    const second = nodesOfKind[tort];
                    const diff = Sub(...first, ...second);
                    const [x1, y1] = Add(...first, ...diff);
                    const [x2, y2] = Sub(...second, ...diff);
    
                    if(!check_coords(singleAntinodeLocations, x1, y1)) singleAntinodeLocations[y1][x1]++;
                    if(!check_coords(singleAntinodeLocations, x2, y2)) singleAntinodeLocations[y2][x2]++;
    
                    // find all resonances
                    // starting
                    resonantAntinodeLocations[first[1]][first[0]]++;
                    resonantAntinodeLocations[second[1]][second[0]]++;
    
                    // go forward
                    let newFirst = [x1, y1] as v2;
                    while(!check_coords(resonantAntinodeLocations, ...newFirst)) {
                        let [x, y] = newFirst;
                        resonantAntinodeLocations[y][x]++;
                        newFirst = Add(...newFirst, ...diff);
                    }
    
                    // go back
                    newFirst = [x2, y2] as v2;
                    while(!check_coords(resonantAntinodeLocations, ...newFirst)) {
                        let [x, y] = newFirst;
                        resonantAntinodeLocations[y][x]++;
                        newFirst = Sub(...newFirst, ...diff);
                    }
                }
            }
        });
    
        const antinodeCount = (prev: number, curr: Array<number>) => prev + curr.reduce((prev, curr) => prev + (curr > 0 ? 1 : 0), 0);
        const part_1 = singleAntinodeLocations.reduce<number>(antinodeCount, 0);
        const part_2 = resonantAntinodeLocations.reduce<number>(antinodeCount, 0);
    
        return {
            part_1, //390
            part_2, //1246
        }
    }
    
    

    Loops on loops on loops on loops…


  • TypeScript

    I wrote my own iterator because I’m a big dummy. Also brute forced (~8s). Might be worth adding a cache to skip all the questions that have been computed / done.

    Solution
    import { AdventOfCodeSolutionFunction } from "./solutions";
    
    function MakeCombination<T>(choices: Array<T>, state: Array<number>): Array<T> {
        return state.map((v) => choices[v]);
    }
    
    function MakeStateArray(length: number) {
        const newArray = [];
        while (length-- > 0)
            newArray.push(0);
    
        return newArray;
    }
    
    function IncrementState(state: Array<number>, max: number): [state: Array<number>, overflow: boolean] {
        state[0]++;
        for (let index = 0; index < state.length; index++) {
            if (state[index] == max) {
                state[index] = 0;
    
                if (index + 1 == state.length)
                    return [state, true];
    
                state[index + 1]++;
            }
        }
    
        return [state, false];
    }
    
    function GenerateCombinations<T>(choices: Array<T>, length: number): Array<Array<T>> {
        const states = MakeStateArray(length);
        const combinations: Array<Array<T>> = [];
    
        let done = false
        while (!done) {
            combinations.push(MakeCombination(choices, states));
            done = IncrementState(states, choices.length)[1];
        }
    
    
        return combinations;
    }
    
    enum Op {
        MUL = "*",
        ADD = "+",
        CON = "|",
    }
    
    function ApplyOp(a: number, b: number, op: Op): number {
        switch (op) {
            case Op.MUL:
                return a * b;
            case Op.ADD:
                return a + b;
            case Op.CON:
                return Number(`${a}${b}`);
        }
    }
    
    function ApplyOperatorsToNumbers(numbers: Array<number>, ops: Array<Op>): number {
        let prev = ApplyOp(numbers[0], numbers[1], ops[0]);
    
        for (let index = 2; index < numbers.length; index++) {
            prev = ApplyOp(prev, numbers[index], ops[index - 1])
        }
    
        return prev;
    }
    
    export const solution_7: AdventOfCodeSolutionFunction = (input) => {
        const numbers = // [{target: 123, numbers: [1, 2, 3, ...]}, ...]
            input.split("\n")
                .map(
                    (v) => v.trim()
                        .split(":")
                        .map(v => v.trim().split(" ").map(v => Number(v)))
                ).map(
                    (v) => {
                        return { target: v[0][0], numbers: v[1] }
                    }
                );
    
        let part_1 = 0;
        let part_2 = 0;
    
        for (let index = 0; index < numbers.length; index++) {
            const target = numbers[index].target;
            const numbs = numbers[index].numbers;
    
            // GenerateCombinations(["+", "*"], 2) => [["+", "+"], ["+", "*"], ["*", "+"], ["*", "*"]]
            const combinations = GenerateCombinations([Op.ADD, Op.MUL], numbs.length - 1); 
    
            // part 1 calculations
            for (let combinationIndex = 0; combinationIndex < combinations.length; combinationIndex++) {
                const combination = combinations[combinationIndex];
                const result = ApplyOperatorsToNumbers(numbs, combination);
                if (result == target) {
                    part_1 += result;
                    break;
                }
            }
    
            const combinations2 = GenerateCombinations([Op.ADD, Op.MUL, Op.CON], numbs.length - 1);
    
            // part 2 calculations
            for (let combinationIndex = 0; combinationIndex < combinations2.length; combinationIndex++) {
                const combination = combinations2[combinationIndex];
                const result = ApplyOperatorsToNumbers(numbs, combination);
                if (result == target) {
                    part_2 += result;
                    break;
                }
            }
    
        }
    
        return {
            part_1,
            part_2,
        }
    }
    


  • TypeScript

    Solution
    import { AdventOfCodeSolutionFunction } from "./solutions";
    
    
    export const check_coords = (grid: Grid, x: number, y: number) => {
        return y >= grid.length ||
            y < 0 ||
            x >= grid[y].length ||
            x < 0
    }
    
    export enum Direction {
        UP,
        UP_RIGHT,
        RIGHT,
        BOTTOM_RIGHT,
        BOTTOM,
        BOTTOM_LEFT,
        LEFT,
        UP_LEFT,
    };
    
    /**
     * This function should return true if it wants the search function to continue
     */
    export type SearchFindFunction = (currChar: string, x: number, y: number) => boolean;
    
    export type Grid = Array<Array<string>>;
    
    export enum SearchExitReason {
        OUT_OF_BOUNDS,
        FUNCTION_FINISHED,
        INVALID_DIRECTION,
    }
    
    export const search_direction = (grid: Grid, x: number, y: number, direction: Direction, find: SearchFindFunction): SearchExitReason => {
        // invalid coords
        if (check_coords(grid, x, y))
            return SearchExitReason.OUT_OF_BOUNDS;
    
        // search failed
        if (!find(grid[y][x], x, y))
            return SearchExitReason.FUNCTION_FINISHED;
    
        switch (direction) {
            case Direction.UP:
                return search_direction(grid, x, y - 1, direction, find);
    
            case Direction.UP_RIGHT:
                return search_direction(grid, x + 1, y - 1, direction, find);
    
            case Direction.RIGHT:
                return search_direction(grid, x + 1, y, direction, find);
    
            case Direction.BOTTOM_RIGHT:
                return search_direction(grid, x + 1, y + 1, direction, find);
    
            case Direction.BOTTOM:
                return search_direction(grid, x, y + 1, direction, find);
    
            case Direction.BOTTOM_LEFT:
                return search_direction(grid, x - 1, y + 1, direction, find);
    
            case Direction.LEFT:
                return search_direction(grid, x - 1, y, direction, find);
    
            case Direction.UP_LEFT:
                return search_direction(grid, x - 1, y - 1, direction, find);
    
            default:
                return SearchExitReason.INVALID_DIRECTION;
        }
    }
    
    export const gridSearch = (grid: Grid, st: SearchFindFunction): [x: number, y: number] => {
        for (let y = 0; y < grid.length; y++) {
            const row = grid[y];
            for (let x = 0; x < row.length; x++) {
                const char = row[x];
                if (!st(char, x, y))
                    return [x, y];
            }
        }
    
        return [-1, -1];
    }
    
    export const makeGridFromMultilineString =
        (input: string) => input.split("\n").map(st => st.trim()).map(v => v.split(""));
    
    export const Duplicate2DArray = <T>(array: Array<Array<T>>) => {
        return [...array.map((item) => [...item])];
    }
    
    
    
    const NextDirection = (dir: Direction) => {
        switch (dir) {
            case Direction.UP:
                return Direction.RIGHT;
            case Direction.RIGHT:
                return Direction.BOTTOM;
            case Direction.BOTTOM:
                return Direction.LEFT;
            case Direction.LEFT:
                return Direction.UP;
            default:
                throw Error("Invalid direction");
        }
    }
    
    /**
     * @returns true if there are no loops
     */
    const NoLoops = (grid: Grid, x: number, y: number, dir: Direction) => {
        const visited = new Set<string>();
    
        /**
         * @returns True if not visited yet
         */
        const addToVisited = (x: number, y: number, dir: Direction) => {
            const log = `${x}:${y}:${dir}`;
    
            if (visited.has(log))
                return false;
    
            visited.add(log);
            return true;
        }
    
        let searchResult: SearchExitReason = SearchExitReason.FUNCTION_FINISHED;
        let res = true;
        let i = 0; // rate limited for API
        let [lastX, lastY] = [x, y];
        while (searchResult !== SearchExitReason.OUT_OF_BOUNDS && i++ < 10_000) {
            searchResult = search_direction(grid, lastX, lastY, dir, (ch, currX, currY) => {
                if (ch == "#")
                    return false;
    
                [lastX, lastY] = [currX, currY];
    
                res = addToVisited(currX, currY, dir);
                return res;
            });
    
            if (!res)
                break;
    
            dir = NextDirection(dir);
        }
    
        return res;
    }
    
    
    export const solution_6: AdventOfCodeSolutionFunction = (input) => {
        const grid = makeGridFromMultilineString(input);
        const visited = new Map<string, [x: number, y: number, dir: Direction, prevX: number, prevY: number]>();
        let [x, y] = gridSearch(grid, (ch) => ch !== "^");
        const [initialX, initialY] = [x, y];
        let dir: Direction = Direction.UP;
    
        const addToVisited = (visitedX: number, visitedY: number, dir: Direction) => {
            const loc = `${visitedX}:${visitedY}`;
            if (!visited.has(loc))
                visited.set(loc, [visitedX, visitedY, dir, x, y]);
        }
    
        addToVisited(x, y, dir);
    
        let res: SearchExitReason = SearchExitReason.FUNCTION_FINISHED;
        let i = 0; // rate limited for API
        while (res !== SearchExitReason.OUT_OF_BOUNDS && i++ < 10_000) {
            res = search_direction(grid, x, y, dir, (ch, currX, currY) => {
                if (ch == "#")
                    return false;
    
                addToVisited(currX, currY, dir);
                [x, y] = [currX, currY];
                return true;
            });
            dir = NextDirection(dir);
        }
    
        const part_1 = visited.size;
    
        // remove starting position for part 1
        visited.delete(`${initialX}:${initialY}`);
    
        let part_2 = 0;
        visited.forEach((v) => {
            const [visitedX, visitedY, visitedDirection, prevX, prevY] = v;
            const newGrid = Duplicate2DArray(grid);
            newGrid[visitedY][visitedX] = "#"; // add a block
    
            // look for loops
            if (!NoLoops(newGrid, prevX, prevY, visitedDirection))
                part_2++;
        });
    
        return {
            part_1, // 4656
            part_2, // 1575
        }
    }
    

    I’m so surprised this runs in ~3s, expected it to take like 60s (do I have supercomputer?). Solution was similar to Pyro’s in this thread as in it simulates the walk then places an obstacle in every possible position but I speed it up by starting just before the obstacle and looking towards it. Also, I reused some code from day 4 by tweaking it slightly. Thank you for the “S” tire Advent of Code puzzle. :)