diff --git a/index.ts b/index.ts index 64fbfdf..e3443c3 100644 --- a/index.ts +++ b/index.ts @@ -1,3 +1,3 @@ -import {runDayEleven} from "./src/day_eleven"; +import {runDayTen} from "./src/day_ten"; -runDayEleven(); \ No newline at end of file +runDayTen(); \ No newline at end of file diff --git a/src/day_ten.test.ts b/src/day_ten.test.ts index 879a79b..57bdb4b 100644 --- a/src/day_ten.test.ts +++ b/src/day_ten.test.ts @@ -1,4 +1,4 @@ -import {getFurthestDistance} from "./day_ten"; +import {getFurthestDistance, tilesContained} from "./day_ten"; describe('Day Ten', () => { it.each([ @@ -21,4 +21,47 @@ LJ...`, 8] const result = getFurthestDistance(input); expect(result).toEqual(expected); }); + + describe('Part Two', () => { + it.each([ + ['.....\n' + + '.S-7.\n' + + '.|.|.\n' + + '.L-J.\n' + + '.....', 1], + ['...........\n' + + '.S-------7.\n' + + '.|F-----7|.\n' + + '.||.....||.\n' + + '.||.....||.\n' + + '.|L-7.F-J|.\n' + + '.|..|.|..|.\n' + + '.L--J.L--J.\n' + + '...........\n', 4], + ['.F----7F7F7F7F-7....\n' + + '.|F--7||||||||FJ....\n' + + '.||.FJ||||||||L7....\n' + + 'FJL7L7LJLJ||LJ.L-7..\n' + + 'L--J.L7...LJS7F-7L7.\n' + + '....F-J..F7FJ|L7L7L7\n' + + '....L7.F7||L7|.L7L7|\n' + + '.....|FJLJ|FJ|F7|.LJ\n' + + '....FJL-7.||.||||...\n' + + '....L---J.LJ.LJLJ...', 8], + ['FF7FSF7F7F7F7F7F---7\n' + + 'L|LJ||||||||||||F--J\n' + + 'FL-7LJLJ||||||LJL-77\n' + + 'F--JF--7||LJLJ7F7FJ-\n' + + 'L---JF-JLJ.||-FJLJJ7\n' + + '|F|F-JF---7F7-L7L|7|\n' + + '|FFJF7L7F-JF7|JL---7\n' + + '7-L-JL7||F7|L7F-7F7|\n' + + 'L.L7LFJ|||||FJL7||LJ\n' + + 'L7JLJL-JLJLJL--JLJ.L', 10] + ])('should count the number of tiles fully contained within the loop', (input, expected) => { + // const result = tilesContained(input); + const result = tilesContained(input); + expect(result).toEqual(expected); + }) + }); }); \ No newline at end of file diff --git a/src/day_ten.ts b/src/day_ten.ts index 4bbc092..412670d 100644 --- a/src/day_ten.ts +++ b/src/day_ten.ts @@ -1,17 +1,24 @@ import fs from "fs"; +import {window} from "./utils"; type Grid = Tile[][]; type Cell = [number, number]; type Tile = 'S' | 'F' | 'J' | 'L' | '7' | '-' | '|' | '.'; +type Loop = string[]; const isTile = (value: string): value is Tile => { return ['S', 'F', 'J', 'L', '7', '-', '|', '.'].includes(value); } const toString = (cell: Cell): string => { - return cell.toString(); + return JSON.stringify(cell); } +const fromString = (cell: string): Cell => { + return JSON.parse(cell); +} + + const getStart = (grid: Grid): Cell => { const row = grid.findIndex(row => row.includes('S')); const col = grid[row].findIndex(cell => cell === 'S'); @@ -28,12 +35,12 @@ const getSurrounding = (grid: Grid, [row, col]: Cell): Cell[] => { [row - 1, col + 1], [row + 1, col - 1], [row + 1, col + 1], - ] as Cell[]).filter((cell) => { - const valid = isValid(grid, cell); - const neighbour = neighbours(grid, cell).map(toString); - - return valid && neighbour.includes([row, col].toString()); - }); + ] as Cell[]).filter((cell) => isInGrid(grid, cell)); +} +const getConnectingNeighbours = (grid: Grid, [row, col]: Cell): Cell[] => { + return getSurrounding(grid, [row, col]).filter((cell) => + isValid(grid, cell) && neighbours(grid, cell).map(toString).includes(toString([row, col])) + ); } const neighbours = (grid: Grid, [row, col]: Cell): Cell[] => { @@ -74,35 +81,71 @@ const neighbours = (grid: Grid, [row, col]: Cell): Cell[] => { } } -const isValid = (grid: Grid, [row, col]: Cell): boolean => { - return row >= 0 && row < grid.length && col >= 0 && col < grid[row].length && grid[row][col] !== '.'; +function isInGrid(grid: Tile[][], [row, col]: Cell) { + return row >= 0 && row < grid.length && col >= 0 && col < grid[row].length; } -export const getFurthestDistance = (input: string): number => { - const grid = input.split('\n').map(line => line.split('').filter(isTile)); +const isValid = (grid: Grid, cell: Cell): boolean => { + return isInGrid(grid, cell) && grid[cell[0]][cell[1]] !== '.'; +} +function parseGrid(input: string) { + return input.split('\n').map(line => line.split('').filter(isTile)); +} + +function getMainLoop(grid: Grid): Loop { const start = getStart(grid); - const queue = getSurrounding(grid, start); - console.log(start, queue); - const visited = new Set([start.toString()]); + const queue = [getConnectingNeighbours(grid, start)[0]]; + const visited = [toString(start)]; while (queue.length > 0) { const current = queue.shift()!; const next = neighbours(grid, current); next.forEach(cell => { - if (!visited.has(cell.toString()) && isValid(grid, cell)) { + if (!visited.includes(toString(cell)) && isValid(grid, cell)) { queue.push(cell); - visited.add(cell.toString()); + visited.push(toString(cell)); } }); } + return visited; +} - return visited.size / 2; +export const getFurthestDistance = (input: string): number => { + const grid = parseGrid(input); + const loop = getMainLoop(grid); + + return loop.length / 2; +} + +export const area = (grid: Grid, loop: Loop): number => { + // Use shoelace formula to calculate area of polygon + const polygon = loop.map(fromString).filter((cell) => { + const value = grid[cell[0]][cell[1]]; + return ['F', 'J', 'L', '7', 'S'].includes(value); + }); + + const total = window([...polygon, polygon[0]], 2).reduce((total, [a, b]) => { + const [y1, x1] = a; + const [y2, x2] = b; + return total + (x1 * y2 - y1 * x2); + }, 0); + + return Math.abs(total) / 2; +} + +export const tilesContained = (input: string): number => { + const grid = parseGrid(input); + const loop = getMainLoop(grid); + + const loopArea = area(grid, loop); + const boundaryPointsCount = loop.length; + return loopArea - (boundaryPointsCount / 2) + 1; } export const runDayTen = () => { const input = fs.readFileSync('./inputs/day_ten_input.txt', 'utf8').trimEnd(); - const result = getFurthestDistance(input); + const result = tilesContained(input); console.log(`Day Ten: ${result}`); } \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index ffd7f0c..430ffc5 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,7 +1,14 @@ export const matchAllAndThen = (input: string, pattern: RegExp, callback: (value: string, index: number) => void) => { - [...input.matchAll(pattern)].map(match => { - if (match && match.index !== undefined) { - callback(match[0], match.index); - } - }); + [...input.matchAll(pattern)].map(match => { + if (match && match.index !== undefined) { + callback(match[0], match.index); + } + }); +} + +export const window = (input: T[], size: number): T[][] => { + return input.reduce((windows, value, index) => { + if (index === input.length - size + 1) return windows; + return [...windows, input.slice(index, index + size)]; + }, [] as T[][]); } \ No newline at end of file