Day 6: Guard Gallivant

Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

FAQ

  • Rin@lemm.ee
    link
    fedilink
    arrow-up
    2
    ·
    21 days ago

    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. :)