Day Ten, Part Two. Finally!
This commit is contained in:
parent
ad781d6a6b
commit
0c8efa7aa5
4
index.ts
4
index.ts
@ -1,3 +1,3 @@
|
|||||||
import {runDayEleven} from "./src/day_eleven";
|
import {runDayTen} from "./src/day_ten";
|
||||||
|
|
||||||
runDayEleven();
|
runDayTen();
|
@ -1,4 +1,4 @@
|
|||||||
import {getFurthestDistance} from "./day_ten";
|
import {getFurthestDistance, tilesContained} from "./day_ten";
|
||||||
|
|
||||||
describe('Day Ten', () => {
|
describe('Day Ten', () => {
|
||||||
it.each([
|
it.each([
|
||||||
@ -21,4 +21,47 @@ LJ...`, 8]
|
|||||||
const result = getFurthestDistance(input);
|
const result = getFurthestDistance(input);
|
||||||
expect(result).toEqual(expected);
|
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);
|
||||||
|
})
|
||||||
|
});
|
||||||
});
|
});
|
@ -1,17 +1,24 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import {window} from "./utils";
|
||||||
|
|
||||||
type Grid = Tile[][];
|
type Grid = Tile[][];
|
||||||
type Cell = [number, number];
|
type Cell = [number, number];
|
||||||
type Tile = 'S' | 'F' | 'J' | 'L' | '7' | '-' | '|' | '.';
|
type Tile = 'S' | 'F' | 'J' | 'L' | '7' | '-' | '|' | '.';
|
||||||
|
type Loop = string[];
|
||||||
|
|
||||||
const isTile = (value: string): value is Tile => {
|
const isTile = (value: string): value is Tile => {
|
||||||
return ['S', 'F', 'J', 'L', '7', '-', '|', '.'].includes(value);
|
return ['S', 'F', 'J', 'L', '7', '-', '|', '.'].includes(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const toString = (cell: Cell): string => {
|
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 getStart = (grid: Grid): Cell => {
|
||||||
const row = grid.findIndex(row => row.includes('S'));
|
const row = grid.findIndex(row => row.includes('S'));
|
||||||
const col = grid[row].findIndex(cell => cell === '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],
|
[row + 1, col - 1],
|
||||||
[row + 1, col + 1],
|
[row + 1, col + 1],
|
||||||
] as Cell[]).filter((cell) => {
|
] as Cell[]).filter((cell) => isInGrid(grid, cell));
|
||||||
const valid = isValid(grid, cell);
|
}
|
||||||
const neighbour = neighbours(grid, cell).map(toString);
|
const getConnectingNeighbours = (grid: Grid, [row, col]: Cell): Cell[] => {
|
||||||
|
return getSurrounding(grid, [row, col]).filter((cell) =>
|
||||||
return valid && neighbour.includes([row, col].toString());
|
isValid(grid, cell) && neighbours(grid, cell).map(toString).includes(toString([row, col]))
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const neighbours = (grid: Grid, [row, col]: Cell): Cell[] => {
|
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 => {
|
function isInGrid(grid: Tile[][], [row, col]: Cell) {
|
||||||
return row >= 0 && row < grid.length && col >= 0 && col < grid[row].length && grid[row][col] !== '.';
|
return row >= 0 && row < grid.length && col >= 0 && col < grid[row].length;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFurthestDistance = (input: string): number => {
|
const isValid = (grid: Grid, cell: Cell): boolean => {
|
||||||
const grid = input.split('\n').map(line => line.split('').filter(isTile));
|
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 start = getStart(grid);
|
||||||
const queue = getSurrounding(grid, start);
|
const queue = [getConnectingNeighbours(grid, start)[0]];
|
||||||
console.log(start, queue);
|
const visited = [toString(start)];
|
||||||
const visited = new Set<string>([start.toString()]);
|
|
||||||
|
|
||||||
while (queue.length > 0) {
|
while (queue.length > 0) {
|
||||||
const current = queue.shift()!;
|
const current = queue.shift()!;
|
||||||
const next = neighbours(grid, current);
|
const next = neighbours(grid, current);
|
||||||
|
|
||||||
next.forEach(cell => {
|
next.forEach(cell => {
|
||||||
if (!visited.has(cell.toString()) && isValid(grid, cell)) {
|
if (!visited.includes(toString(cell)) && isValid(grid, cell)) {
|
||||||
queue.push(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 = () => {
|
export const runDayTen = () => {
|
||||||
const input = fs.readFileSync('./inputs/day_ten_input.txt', 'utf8').trimEnd();
|
const input = fs.readFileSync('./inputs/day_ten_input.txt', 'utf8').trimEnd();
|
||||||
const result = getFurthestDistance(input);
|
const result = tilesContained(input);
|
||||||
console.log(`Day Ten: ${result}`);
|
console.log(`Day Ten: ${result}`);
|
||||||
}
|
}
|
17
src/utils.ts
17
src/utils.ts
@ -1,7 +1,14 @@
|
|||||||
export const matchAllAndThen = (input: string, pattern: RegExp, callback: (value: string, index: number) => void) => {
|
export const matchAllAndThen = (input: string, pattern: RegExp, callback: (value: string, index: number) => void) => {
|
||||||
[...input.matchAll(pattern)].map(match => {
|
[...input.matchAll(pattern)].map(match => {
|
||||||
if (match && match.index !== undefined) {
|
if (match && match.index !== undefined) {
|
||||||
callback(match[0], match.index);
|
callback(match[0], match.index);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const window = <T>(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[][]);
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user