Skip to content

feat: day 10 hoof it 20241210 #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

</details>

Expand Down Expand Up @@ -101,7 +102,7 @@ Each Advent of Code (AOC) event quiz has its folder under **`"/src/<YEAR>/<YYYY-
Using Node

1. (Optional) Replace the values of specific `input.txt` in the `"/src/<YEAR>/<YYYY-MM-DD>"` 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
```
Expand Down
4 changes: 4 additions & 0 deletions src/2024/2024-12-09/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
18 changes: 18 additions & 0 deletions src/2024/2024-12-10/README.md
Original file line number Diff line number Diff line change
@@ -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<br>
**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`
Binary file added src/2024/2024-12-10/assets/grid_01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/2024/2024-12-10/assets/grid_02.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/2024/2024-12-10/assets/grid_03.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/2024/2024-12-10/assets/grid_04.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/2024/2024-12-10/input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
89010123
78121874
87430965
96749874
45278903
32019012
01329801
10456732
105 changes: 105 additions & 0 deletions src/2024/2024-12-10/lib/scoresRatings.ts
Original file line number Diff line number Diff line change
@@ -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<string, string[]> = {}
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
}
}
60 changes: 60 additions & 0 deletions src/2024/2024-12-10/lib/types.ts
Original file line number Diff line number Diff line change
@@ -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<string, string[]>} 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<string, string[]>,
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;
}
79 changes: 79 additions & 0 deletions src/2024/2024-12-10/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -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
)
})
}
38 changes: 38 additions & 0 deletions src/2024/2024-12-10/main.ts
Original file line number Diff line number Diff line change
@@ -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()
23 changes: 23 additions & 0 deletions src/2024/2024-12-10/sample.test.ts
Original file line number Diff line number Diff line change
@@ -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)
})
6 changes: 3 additions & 3 deletions src/utils/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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
Expand Down
Loading