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
- What is this?: Here is a post with a large amount of details: https://programming.dev/post/6637268
- Where do I participate?: https://adventofcode.com/
- Is there a leaderboard for the community?: We have a programming.dev leaderboard with the info on how to join in this post: https://programming.dev/post/6631465
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. :)