diff --git a/README.md b/README.md index 1b4f4da..5b1454a 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ The codes are structured in a way that discusses and walks through the solution - Day 7: Bridge Repair [[link]](/src/2024/2024-12-07/README.md) - Day 8: Resonant Collinearity [[link]](/src/2024/2024-12-08/README.md) - Day 9: Disk Fragmenter [[link]](/src/2024/2024-12-09/README.md) +- Day 10: Hoof It [[link]](/src/2024/2024-12-10/README.md) @@ -101,7 +102,7 @@ Each Advent of Code (AOC) event quiz has its folder under **`"/src///"` directories with actual AOC input. -2. Run a non-test TypeScript file inside the **/src** directory. For example: +2. Run a non-test TypeScript file inside the **/src** directory from the project's _**"root directory"**_. For example: ``` npx vite-node src/sample/sample.ts ``` diff --git a/src/2024/2024-12-09/main.ts b/src/2024/2024-12-09/main.ts index 8154672..7d13b4e 100644 --- a/src/2024/2024-12-09/main.ts +++ b/src/2024/2024-12-09/main.ts @@ -22,6 +22,10 @@ export const quiz20241209_01 = () => { console.log('Compacted disk checksum:', sum) } +/** + * Part 2/2 of the 2024-12-09 quiz + * Counts the check sum of a defragment disk whose file blocks were moved to empty spaces as whole blocks + */ export const quiz20241209_02 = () => { const disk = new WholeDisk(input, true) const sum = disk.calculateDiskChecksum() diff --git a/src/2024/2024-12-10/README.md b/src/2024/2024-12-10/README.md new file mode 100644 index 0000000..5faaddb --- /dev/null +++ b/src/2024/2024-12-10/README.md @@ -0,0 +1,18 @@ +## Day 10: Hoof It + +Visit the Advent of Code website for more information on this puzzle at: + +**Source:** https://adventofcode.com/2024/day/10
+**Status:** Complete ⭐⭐ + +## Code + +### `utils.ts` + +- Utility and helper functions. + +### `scoresRatings.ts` + +- `countTrailScores()` + - Finds valid hiking trails (trailheads) from a point coordinate in a 2D array starting with `0` and ending in `9` symbol and calculates the **scores** for each trailhead. + - Calculates the trailhead **ratings** instead of the trailhead scores if provided with the optional `{ isRating: true }` parameter. Defaults to `false` diff --git a/src/2024/2024-12-10/assets/grid_01.png b/src/2024/2024-12-10/assets/grid_01.png new file mode 100644 index 0000000..d5e068b Binary files /dev/null and b/src/2024/2024-12-10/assets/grid_01.png differ diff --git a/src/2024/2024-12-10/assets/grid_02.png b/src/2024/2024-12-10/assets/grid_02.png new file mode 100644 index 0000000..b87edf4 Binary files /dev/null and b/src/2024/2024-12-10/assets/grid_02.png differ diff --git a/src/2024/2024-12-10/assets/grid_03.png b/src/2024/2024-12-10/assets/grid_03.png new file mode 100644 index 0000000..cafb93b Binary files /dev/null and b/src/2024/2024-12-10/assets/grid_03.png differ diff --git a/src/2024/2024-12-10/assets/grid_04.png b/src/2024/2024-12-10/assets/grid_04.png new file mode 100644 index 0000000..7954e75 Binary files /dev/null and b/src/2024/2024-12-10/assets/grid_04.png differ diff --git a/src/2024/2024-12-10/input.txt b/src/2024/2024-12-10/input.txt new file mode 100644 index 0000000..da9f063 --- /dev/null +++ b/src/2024/2024-12-10/input.txt @@ -0,0 +1,8 @@ +89010123 +78121874 +87430965 +96749874 +45278903 +32019012 +01329801 +10456732 \ No newline at end of file diff --git a/src/2024/2024-12-10/lib/scoresRatings.ts b/src/2024/2024-12-10/lib/scoresRatings.ts new file mode 100644 index 0000000..8d3176a --- /dev/null +++ b/src/2024/2024-12-10/lib/scoresRatings.ts @@ -0,0 +1,105 @@ +import type { Point } from '../../2024-12-08/lib/types.js' +import type { InputOptions, PointSteps, PointDirection, TrailScores } from './types.js' + +import { + findValidSteps, + findZeroCoordinatePositions, + getCoordinateSymbol +} from './utils.js' + +// List of trailhead scores +const scores: Record = {} +let activeZeroIndex = '' + +/** + * Finds valid hiking trails (trailheads) from a point coordinate in a 2D array starting `0` and ending in `9` symbols and + * calculates the scores for each trailhead. + * @param {PointDirection} pointVector - Point (y,x) coordinate in a 2D array with a list of valid coordinates from its location. + * @param {number[][]} data - 2D number array containing hiking trail data + * @param {boolean} isRating - If `true`, calculates the trailhead ratings instead of the trailhead scores. Defaults to `false` + * @returns {void} + */ +const findPaths = (pointVector: PointDirection, data: number[][], isRating: boolean = false) => { + const grid = { + length: data.length, width: data[0]!.length + } + + if (pointVector.validSteps.length > 0) { + while (pointVector.validSteps.length > 0) { + const step = pointVector.validSteps.pop() + + if (step === undefined) continue + const pt = getCoordinateSymbol(step, data) + + if (pt.symbol === 9) { + if (isRating) { + // Rating: count all trails ending in 9's + scores[activeZeroIndex]?.push(pt.coordinate) + } else { + // Scores: count unique ending 9's that match with the starting 0 + if (!scores[activeZeroIndex]!.includes(pt.coordinate)) { + scores[activeZeroIndex]?.push(pt.coordinate) + } + } + } + + const point: PointDirection = { + x: step!.x, + y: step!.y, + validSteps: findValidSteps(step as Point, grid, data) as PointSteps[] + } + + findPaths(point, data, isRating) + } + } +} + +/** + * Finds valid trailheads and counts each trailhead score. + * @param {number[][]} data - 2D number array containing hiking trail data + * @param {boolean} [printLog] - Flag to display the processing and total score logs + * @typedef {InputOptions} params - Input and logging parameter options + * @param {boolean} [params.printLog] - (Optional) Flag to display the miscellaneous data processing logs. + * @param {boolean} [params.isRating] - (Optional) Flag to calculate the trailhead rating instead of the score. + * @returns {TrailScores} + */ +export const countTrailScores = (data: number[][], params?: InputOptions): TrailScores => { + // Find starting positions + const starts = findZeroCoordinatePositions(data) + + const grid = { + length: data.length, width: data[0]!.length + } + + for (let i = 0; i < starts.length; i += 1) { + const initStep: PointDirection = { + x: starts[i]!.x, + y: starts[i]!.y, + validSteps: findValidSteps(starts[i] as Point, grid, data) as PointSteps[] + } + + const pt = getCoordinateSymbol(starts[i] as Point, data) + activeZeroIndex = pt.coordinate + scores[activeZeroIndex] = [] + + findPaths(initStep, data, params?.isRating) + } + + const total = Object + .values(scores) + .map(x => x.length) + .reduce((sum, item) => sum += item, 0) + + if (params?.printLog) { + for (const key in scores) { + console.log(`[${key}]: ${scores[key]?.length} score`) + } + + console.log('--TOTAL SCORE', total) + } + + return { + scores, + total + } +} diff --git a/src/2024/2024-12-10/lib/types.ts b/src/2024/2024-12-10/lib/types.ts new file mode 100644 index 0000000..5318951 --- /dev/null +++ b/src/2024/2024-12-10/lib/types.ts @@ -0,0 +1,60 @@ +/** + * Point (y,x) coordinate in a 2D array with direction + * @type {Object} PointSteps + * @property {number} x - x-coordinate of the point + * @property {number} y - y-coordinate of the point + * @property {Object} direction - Direction vector + * @property {number} direction.x - Left/right x-direction denoted by `+1` or `-1` + * @property {number} direction.y - Up/down y-direction denoted by `-1` or `+1` + */ +export type PointSteps = { + x: number; + y: number; + direction: { + x: number; + y: number; + } +} + +/** + * Point (y,x) coordinate in a 2D array with a list of valid coordinates from its location. + * @type {Object} PointDirection + * @property {number} x - x-coordinate of the point + * @property {number} y - y-coordinate of the point + * @property {PointSteps[]} validSteps - List of valid up/down/left/right coordinates from the (y,x) position. + */ +export type PointDirection = { + x: number; + y: number; + validSteps: PointSteps[] +} + +/** + * Represents a "(y,x)" coordinate in string and its value from a 2D array. + * @param {string} coordinate - String version of an "(y,x)" coordinate + * @param {string | number} symbol - Number or character in a 2D arary denoted by the (y,x) coordinate + */ +export type GridCoordinateSymbol = { + coordinate: string; + symbol: string | number; +} + +/** + * Data returned by the trailhead scores counting function. + * @param {Record} scores - Object list of trailhead scores per (y,x) coordinate that starts with a unique `0` and ends with a `9` + * @param {number} total - Total sum of the `scores` + */ +export type TrailScores = { + scores: Record, + total: number; +} + +/** + * Input settings options parameters for the Day 10 quiz + * @param {boolean} [printLog] - (Optional) Flag to display the miscellaneous data processing logs. + * @param {boolean} [isRating] - (Optional) Flag to calculate the trailhead rating instead of the score. + */ +export type InputOptions = { + printLog?: boolean; + isRating?: boolean; +} diff --git a/src/2024/2024-12-10/lib/utils.ts b/src/2024/2024-12-10/lib/utils.ts new file mode 100644 index 0000000..dcb90f6 --- /dev/null +++ b/src/2024/2024-12-10/lib/utils.ts @@ -0,0 +1,79 @@ +import type { Point } from '@/2024/2024-12-08/lib/types.js' +import type { GridDimensions } from '@/2024/2024-12-06/lib/grid.types.js' +import type { GridCoordinateSymbol, PointSteps } from './types.js' + +/** + * Converts a 2D `Point` point object to string and returns its value from the 2D array + * @param {Point} point - (y,x) coordinatate in the 2D array + * @param {number[][]} data - 2D number array containing hiking trail data + * @returns {GridCoordinateSymbol} Returns the `poiint` (x,y) coordinate expressed in string and its value + */ +export const getCoordinateSymbol = (point: Point, data: number[][]): GridCoordinateSymbol => { + return { + coordinate: `${point!.x},${point!.y}`, + symbol: data[point!.y]![point!.x] as number + } +} + +/** + * Finds the (y,x) coordinates of starting positions in a trailhead grid + * @param {number[][]} data - 2D number array containing hiking trail data + * @param {number} [symbol] - (Optional) Number indicating the symbol of a trailhead's start. Defaults to `0`. + * @returns {Point[]} Array of (y,x) coortinates of starting positions + */ +export const findZeroCoordinatePositions = (data: number[][], symbol?: number): Point[] => { + return data.reduce((list: Point[], row, y) => { + const rowItems = row.reduce((inner: Point[], item, x) => { + if (item === (symbol ?? 0)) return [...inner, { y, x }] + return inner + }, []) + + return [...list, ...rowItems] + }, []) +} + +/** + * Checks if a (y,x) coordinate is out of the grid area + * @param {Point} point - (y,x) coordinate + * @param {GridDimensions} gridMeta - Length and width definitions of a 2D array (grid) + * @returns {boolean} Flag if a coordinate is out of the grid area + */ +export const isOutOfBounds = (point: Point, gridMeta: GridDimensions): boolean => { + return ( + point.x < 0 || point.x >= gridMeta.width || + point.y < 0 || point.y >= gridMeta.length + ) +} + +/** + * Finds valid positions and coordinates of next steps from a coordinate. + * @param {Point} point - (y,x) coordinate object in a 2D array grid + * @param {GridDimensions} gridMeta - grid length and width + * @param {number[][]} grid - 2D number array input + * @returns {PointSteps[]} Array of valid grid positions from the given `point` + */ +export const findValidSteps = ( + point: Point, + gridMeta: GridDimensions, + grid: number[][] +): PointSteps[] | undefined => { + if (isOutOfBounds(point, gridMeta)) return + + // All 4 possible locations (directions) from a coordinate + const steps = [ + { ...point, x: point.x - 1, direction: { x: -1, y: 0 } }, // left + { ...point, x: point.x + 1, direction: { x: 1, y: 0 } }, // right + { ...point, y: point.y - 1, direction: { x: 0, y: -1 } }, // down + { ...point, y: point.y + 1, direction: { x: 0, y: 1 } } // up + ] + + // Filter valid items + return steps.filter(step => { + return ( + // Within the grid area + !isOutOfBounds(step, gridMeta) && + // Difference (elevation) with the current grid value is 1 + (grid[step.y]![step.x] ?? 0) - (grid[point.y]![point.x] ?? 0) === 1 + ) + }) +} diff --git a/src/2024/2024-12-10/main.ts b/src/2024/2024-12-10/main.ts new file mode 100644 index 0000000..13621d6 --- /dev/null +++ b/src/2024/2024-12-10/main.ts @@ -0,0 +1,38 @@ + +import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' +import { file } from '@/utils/file.js' + +import { countTrailScores } from './lib/scoresRatings.js' + +const input = readAOCInputFile({ + filePath: file(import.meta.url, 'input.txt'), + type: AOC_OUTPUT_TYPE.NUMBER_ARRAY_2D +}) as number[][] + +/** + * Part 1/2 of the 2024-12-10 quiz + * Counts the total trailhead scores + */ +const quiz20241210_01 = () => { + const totalScore = countTrailScores(input, { + printLog: true + }) + + console.log('Total trailhead score:', totalScore.total) +} + +/** + * Part 2/2 of the 2024-12-10 quiz + * Counts the total trailhead ratings + */ +const quiz20241210_02 = () => { + const totalScore = countTrailScores(input, { + printLog: true, + isRating: true + }) + + console.log('Total trailhead ratings:', totalScore.total) +} + +quiz20241210_01() +quiz20241210_02() diff --git a/src/2024/2024-12-10/sample.test.ts b/src/2024/2024-12-10/sample.test.ts new file mode 100644 index 0000000..8b456c8 --- /dev/null +++ b/src/2024/2024-12-10/sample.test.ts @@ -0,0 +1,23 @@ +import { test, expect } from 'vitest' + +import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' +import { file } from '@/utils/file.js' + +import { countTrailScores } from './lib/scoresRatings.js' + +const input = readAOCInputFile({ + filePath: file(import.meta.url, 'input.txt'), + type: AOC_OUTPUT_TYPE.NUMBER_ARRAY_2D +}) as number[][] + +test('Total trailhead score:', () => { + expect( + countTrailScores(input).total + ).toBe(17) +}) + +test('Trailhead rating:', () => { + expect( + countTrailScores(input, { isRating: true }).total + ).toBe(25) +}) diff --git a/src/utils/file.ts b/src/utils/file.ts index 7b65032..1530bdc 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -3,8 +3,8 @@ import path, { dirname } from 'path' import { fileURLToPath } from 'url' /** - * Get the full file path of the current directory of a module file equivalent to `"__dirname"`. - * (scripts running as ESM modules whose package.json has `"type": "module"`) + * Get the full file path of the current directory of a module file equivalent to `"__dirname"`from + * scripts running as ESM modules whose package.json has `"type": "module"`. * @param {string} moduleFile - File URL of the current module being executed: `"import.meta.url"` * @returns {string} Full file path to the directory of the calling file/module also know as `__dirname` in CommonJS */ @@ -23,7 +23,7 @@ export const readFile = (pathToFile: string): string => { } /** - * Get the full system file path to a file + * Returns the full system file path to a file * @param {string} moduleFile - File URL of the current module being executed: `"import.meta.url"` * @param {string} fileName - File name relative to the calling directory (`moduleFile`), eg: `input.txt`, `../input.txt`, or `some/folder/input.txt` * @returns {string} Full file path to a file