From b5b9ef3edc735c67ec4712aefca13f1d358cd4cf Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Tue, 17 Dec 2024 22:24:23 +0800 Subject: [PATCH 01/33] ref: move fxn out of loop --- src/2024/2024-12-05/lib/fixOrderingUpdates.ts | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/2024/2024-12-05/lib/fixOrderingUpdates.ts b/src/2024/2024-12-05/lib/fixOrderingUpdates.ts index 98a6550..b7001eb 100644 --- a/src/2024/2024-12-05/lib/fixOrderingUpdates.ts +++ b/src/2024/2024-12-05/lib/fixOrderingUpdates.ts @@ -10,7 +10,7 @@ type CurrentItem = { } /** - * Finds the next appropriate array index to swap places with the incorrectly placed current item accoring to the "rules" + * Finds the next appropriate array index to swap places with the incorrectly placed current item according to the "rules" * @param rules {Rules} Object containing parsed and formatted rules and updates data * @param restItems {number[]} "updates" array items content * @param currentItem {CurrentItem} Object containing the "current" item data in focus: value and array index @@ -44,30 +44,32 @@ export const fixOrdering = (rules: Rules, unorderedItems: number[]): number[] => throw new Error('Invalid item/s') } - for (let i = 0; i < unorderedItems.length - 1; i += 1) { - let currentItem = unorderedItems[i] as number - const currentItemData = { value: currentItem, index: i } + let currentItem: number = -2 - // Swaps incorrectly placed items with target items in the array - const swapItems = () => { - const indexToSwap = nextHotSwapIndex( - rules, - unorderedItems, - currentItemData - ) + // Swaps incorrectly placed items with target items in the array + const swapItems = (activeItem: CurrentItem, activeIndex: number) => { + const indexToSwap = nextHotSwapIndex( + rules, + unorderedItems, + activeItem + ) - const temp = unorderedItems[indexToSwap] as number - unorderedItems[indexToSwap] = currentItem - unorderedItems[i] = temp - currentItem = temp + const temp = unorderedItems[indexToSwap] as number + unorderedItems[indexToSwap] = activeItem.value + unorderedItems[activeIndex] = temp + currentItem = temp - fixOrdering(rules, unorderedItems) - } + fixOrdering(rules, unorderedItems) + } + + for (let i = 0; i < unorderedItems.length - 1; i += 1) { + currentItem = unorderedItems[i] as number + const currentItemData = { value: currentItem, index: i } // Correct "update" item should have en entry in the "rules" object // Swap places with other items if its incorrect if (rules[currentItem] === undefined) { - swapItems() + swapItems(currentItemData, i) } // Get the rest of items after the current item for comparison @@ -76,7 +78,7 @@ export const fixOrdering = (rules: Rules, unorderedItems: number[]): number[] => // Correct item's "rule" should have the after-item entries // Swap places with other items if its incorrect if (!afterItems.every(item => rules[currentItem]?.includes(item))) { - swapItems() + swapItems(currentItemData, i) } } From 9b6c82ad195e585e6a9af6e68c39c311cb1d517a Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Tue, 17 Dec 2024 22:26:21 +0800 Subject: [PATCH 02/33] chore: update notes --- src/utils/arrays.ts | 12 ++++++++---- src/utils/file.ts | 7 ++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/utils/arrays.ts b/src/utils/arrays.ts index 5748cd4..f8ce6b2 100644 --- a/src/utils/arrays.ts +++ b/src/utils/arrays.ts @@ -20,14 +20,18 @@ export const arrangeArray = (order: ARRAY_ORDERING) => } /** - * Checks if array elements have the same type and has no null or undefined values + * Checks if array elements have the same type using `typeof` and has no null or undefined values * @param items {S[]} array of elements - * @param type {T} type name of the elements inside the `items` array + * @param type {T} primitive type name of the elements inside the array (e.g., number, string, boolean) * @returns {boolean} Flag indicating if all array elements have the same type */ -export const uniformArrayElements = (items: S[], type: T): boolean => { +export const uniformArrayElements = ( + items: S[], + type: T +): boolean => { return ( - items.filter(value => typeof value === type).length === items.length + items.filter(value => typeof value === type) + .length === items.length ) } diff --git a/src/utils/file.ts b/src/utils/file.ts index 247667b..761ad13 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -3,9 +3,10 @@ import { dirname } from 'path' import { fileURLToPath } from 'url' /** - * Get the full file path of the current directory. + * Get the full file path `"__dirname"` of the current directory of a module file + * (scripts running as ESM modules whose package.json has `"type": "module"`) * @param moduleFile {string} File URL of the current module being executed: `"import.meta.url"` - * @returns Full file path to the directory of the calling file/module + * @returns Full file path to the directory of the calling file/module also know as `__dirname` in CommonJS */ export const currentDirectory = (moduleFile: string): string => { const filePath = fileURLToPath(moduleFile) @@ -15,7 +16,7 @@ export const currentDirectory = (moduleFile: string): string => { /** * Reads file from disk * @param pathToFile Full file path to a target file - * @returns {string} String version of the file + * @returns {string} String version of the file contents */ export const readFile = (pathToFile: string): string => { return fs.readFileSync(pathToFile, 'utf-8') From d97e4c105949026189cbcbd7b459decce044fcf6 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Wed, 18 Dec 2024 19:24:56 +0800 Subject: [PATCH 03/33] chore: common aoc file input reader --- src/utils/aocInputFile.ts | 64 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/utils/aocInputFile.ts diff --git a/src/utils/aocInputFile.ts b/src/utils/aocInputFile.ts new file mode 100644 index 0000000..4a0fb5a --- /dev/null +++ b/src/utils/aocInputFile.ts @@ -0,0 +1,64 @@ +// This code contains file input readers for common AoC input types. +import { readFile } from './file.js' + +export enum AOC_OUTPUT_TYPE { + STRING = 'string', + STRING_ARRAY = 'string_array', + STRING_ARRAY_2D = '2d_string_array', + NUMBER_ARRAY = 'number_array', + NUMBER_ARRAY_2D = '2d_number_array' +} + +type FileInput = { + /** @param filePath {string} Full file path to input file */ + filePath: string; + type: AOC_OUTPUT_TYPE; + delimiter?: string; +} + +type Output = string | string[] | string[][] | + number[] | number[][] + +/** + * Reads common AoC input text files. + * @typedef param {FileInput} File input definitions + * @param param.filePath {string} Full file path to an input text file + * @param param.type {AOC_OUTPUT_TYPE} Type to convert the input text file + * @param param.delimiter {string} String delimiter + * @param moduleFile {string} File URL of the current module being executed: `"import.meta.url"` + * @throws {Error} + */ +export const readAOCInputFile = (param: FileInput): Output => { + const file = readFile(param.filePath) + const delimiter = param?.delimiter ?? '' + + if (file === undefined) { + throw new Error('Undefined file') + } + + switch(param.type) { + case AOC_OUTPUT_TYPE.STRING: + return file as string + + case AOC_OUTPUT_TYPE.STRING_ARRAY: + return file.split(delimiter) as string[] || [] + + case AOC_OUTPUT_TYPE.STRING_ARRAY_2D: + return file + .split('\n') + .map(row => row.split(delimiter)) as string[][] || [] + + case AOC_OUTPUT_TYPE.NUMBER_ARRAY: + return file + .split('') + .map(Number) as number[] || [] + + case AOC_OUTPUT_TYPE.NUMBER_ARRAY_2D: + return file + .split('\n') + .map(row => row.split(delimiter).map(Number)) as number[][] || [] + + default: + throw new Error('Unsupported type') + } +} From 854190b617be103e95849623d3219ca65eea9fb9 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Wed, 18 Dec 2024 19:27:01 +0800 Subject: [PATCH 04/33] feat: day 6 guard gallivant 1/2 soln, #11 --- src/2024/2024-12-06/README.md | 6 ++ src/2024/2024-12-06/input.txt | 10 +++ src/2024/2024-12-06/lib/grid.ts | 100 +++++++++++++++++++++ src/2024/2024-12-06/lib/guard.ts | 81 +++++++++++++++++ src/2024/2024-12-06/lib/guardController.ts | 59 ++++++++++++ src/2024/2024-12-06/main.ts | 23 +++++ src/2024/2024-12-06/sample.test.ts | 16 ++++ 7 files changed, 295 insertions(+) create mode 100644 src/2024/2024-12-06/README.md create mode 100644 src/2024/2024-12-06/input.txt create mode 100644 src/2024/2024-12-06/lib/grid.ts create mode 100644 src/2024/2024-12-06/lib/guard.ts create mode 100644 src/2024/2024-12-06/lib/guardController.ts create mode 100644 src/2024/2024-12-06/main.ts create mode 100644 src/2024/2024-12-06/sample.test.ts diff --git a/src/2024/2024-12-06/README.md b/src/2024/2024-12-06/README.md new file mode 100644 index 0000000..e3da586 --- /dev/null +++ b/src/2024/2024-12-06/README.md @@ -0,0 +1,6 @@ +## Day 6: Guard Gallivant + +Visit the Advent of Code website for more information on this puzzle at: + +**Source:** https://adventofcode.com/2024/day/6
+**Status:** On-going ⭐ diff --git a/src/2024/2024-12-06/input.txt b/src/2024/2024-12-06/input.txt new file mode 100644 index 0000000..dc1b519 --- /dev/null +++ b/src/2024/2024-12-06/input.txt @@ -0,0 +1,10 @@ +....#..... +.#.......# +....^..... +..#....... +....#..#.. +.......... +.#........ +....#...#. +#......... +......#... \ No newline at end of file diff --git a/src/2024/2024-12-06/lib/grid.ts b/src/2024/2024-12-06/lib/grid.ts new file mode 100644 index 0000000..b93148c --- /dev/null +++ b/src/2024/2024-12-06/lib/grid.ts @@ -0,0 +1,100 @@ +import { GuardStatus, GuardDirection } from './guard.js' +import type { GuardState } from './guard.js' + +export type GridDimensions = { + length: number; + width: number; +} + +export class Grid { + board: string[][] = [] + length: number = 0 + width: number = 0 + positionCount: number = 0 + + obstruction = '#' + pathSymbol = '.' + + constructor (data: string[][]) { + this.board = data + this.length = data.length + this.width = (data[0])?.length as number + } + + /** + * Finds the initial position and state of the guard in a 2D board array + * @param data {string[][]} 2D string array of the guard board + * @returns {GuardState} Initial position and state of the guard + */ + findGuardStartPosition (): GuardState { + const GuardDirections: string[] = Object.values(GuardDirection) + + const initialState: GuardState = { + direction: null, + xPos: -1, + yPos: -1, + status: GuardStatus.IDLE + } + + for (let y = 0; y < this.board.length; y += 1) { + const row = this.board[y] + + if (row === undefined) { + throw new Error('Undefined row') + } + + for (let i = 0; i < GuardDirections.length; i += 1) { + const indexOfDirection = row.indexOf(GuardDirections[i] as string) + + if (indexOfDirection >= 0) { + initialState.direction = GuardDirections[i] as GuardDirection + initialState.xPos = indexOfDirection + initialState.yPos = y + initialState.status = GuardStatus.ACTIVE + break + } + } + + if (initialState.status === GuardStatus.ACTIVE) break + } + + return initialState + } + + /** + * Checks if an (x,y) coordinate is outside the grid area + * @param {number} x array x coordinate + * @param {number} y array y coordinate + * @returns {boolean} Flag indicating if the guard is outside the grid + */ + isOutOfBounds (x: number, y: number): boolean { + const row = this.board[0] as string[] + + return x < 0 || + x >= row.length || + y < 0 || + y >= this.board.length + } + + /** + * Marks an (x,y) coordinate in the grid + * @param {number} x array x coordinate + * @param {number} y array y coordinate + * @returns {void} + */ + mark (x: number, y: number): void { + const row = this.board[y] + + if (row !== undefined && row[x] !== 'X') { + row[x] = 'X' + this.positionCount += 1 + } + } + + /** + * Displays the guard's distinct positions in the grid + */ + print () { + console.log(this.board.map(row => row.join(' '))) + } +} diff --git a/src/2024/2024-12-06/lib/guard.ts b/src/2024/2024-12-06/lib/guard.ts new file mode 100644 index 0000000..53d06c2 --- /dev/null +++ b/src/2024/2024-12-06/lib/guard.ts @@ -0,0 +1,81 @@ +export enum GuardDirection { + UP = '^', + RIGHT = '>', + DOWN = 'v', + LEFT = '<' +} + +export enum GuardStatus { + IDLE = 'idle', + ACTIVE = 'active', + EXIT = 'exit' +} + +export type GuardState = { + direction: GuardDirection | null; + xPos: number; + yPos: number; + status: GuardStatus; +} + +/** + * @class Guard + * @description Game object that can move and mark its trail in a 2D grid + */ +export class Guard { + direction: GuardDirection | null = null + xPos: number = 0 + yPos: number = 0 + status: GuardStatus = GuardStatus.IDLE + + /** + * @constructor + * @param {GuardState} state - Initial state of the guard + * @param {GridDimensions} [gridDimensions={ length: 5, width: 5 }] - Dimensions of the grid + */ + constructor ( + state: GuardState + ) { + this.direction = state.direction + this.xPos = state.xPos || 0 + this.yPos = state.yPos || 0 + this.status = state.status + } + + /** + * Turn the guard's direction by rotating clockwise once + * @returns {void} + */ + turn (): void { + if (this.direction === null) return + + const directions = Object.values(GuardDirection) + const currentDirectionIndex = directions.indexOf(this.direction) + + const nextIndex = (currentDirectionIndex + 1) < directions.length + ? currentDirectionIndex + 1 + : 0 + + this.direction = directions[nextIndex] as GuardDirection + } + + /** + * Moves the guard one (1) coordinate [x,y] position in the grid and marks the new position + * @param xDirection {number} x coordinate + * @param yDirection {number} y coordinate + * @returns {void} + */ + walk (xDirection: number, yDirection: number): void { + if (xDirection === undefined || yDirection === undefined) return + + this.xPos += xDirection + this.yPos += yDirection + } + + /** + * Set's the guard's status to "exited" + */ + exit (): void { + this.status = GuardStatus.EXIT + } +} diff --git a/src/2024/2024-12-06/lib/guardController.ts b/src/2024/2024-12-06/lib/guardController.ts new file mode 100644 index 0000000..50eea24 --- /dev/null +++ b/src/2024/2024-12-06/lib/guardController.ts @@ -0,0 +1,59 @@ + + +import { Guard, GuardDirection } from './guard.js' +import { Grid } from './grid.js' +import { GuardStatus } from './guard.js' + +/** + * + * @param {string[][]} data 2D string array containing the guards grid paths + * @param {boolean} printTrail Flag to display the distinct guard positions in the grid + * @returns {number} Number of unique guard positions + */ +export const guardController = (data: string[][], printTrail: boolean = false): number => { + const grid = new Grid(data) + const guard = new Guard(grid.findGuardStartPosition()) + + while ( + guard.status !== GuardStatus.EXIT && + !grid.isOutOfBounds(guard.xPos, guard.yPos) + ) { + let yDirection = 0 + let xDirection = 0 + + switch(guard.direction) { + case GuardDirection.UP: + yDirection = -1 + break + case GuardDirection.DOWN: + yDirection = 1 + break + case GuardDirection.LEFT: + xDirection = -1 + break + case GuardDirection.RIGHT: + xDirection = 1 + break + } + + if (grid.board[guard.yPos + yDirection]?.[guard.xPos + xDirection] !== grid.obstruction) { + if ( + guard.yPos === grid.board.length - 1 && + guard.xPos === (grid.board[0] as string[])?.length - 1 + ) { + guard.exit() + } else { + grid.mark(guard.xPos, guard.yPos) + guard.walk(xDirection, yDirection) + } + } else { + guard.turn() + } + } + + if (printTrail) { + grid.print() + } + + return grid.positionCount +} diff --git a/src/2024/2024-12-06/main.ts b/src/2024/2024-12-06/main.ts new file mode 100644 index 0000000..6aeaf60 --- /dev/null +++ b/src/2024/2024-12-06/main.ts @@ -0,0 +1,23 @@ +import path from 'path' +import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' +import { currentDirectory } from '@/utils/file.js' + +import { guardController } from './lib/guardController.js' + +const file = readAOCInputFile({ + filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + type: AOC_OUTPUT_TYPE.STRING_ARRAY_2D +}) as string [][] + +/** + * Part 1/2 of the 2024-12-06 quiz + * Counts the number of distinct guard positions in a grid + */ +export const quiz20241206_01 = () => { + const positionCount = guardController(file, true) + + console.log('Distinct guard positions', positionCount) + return positionCount +} + +quiz20241206_01() diff --git a/src/2024/2024-12-06/sample.test.ts b/src/2024/2024-12-06/sample.test.ts new file mode 100644 index 0000000..8ed908e --- /dev/null +++ b/src/2024/2024-12-06/sample.test.ts @@ -0,0 +1,16 @@ +import path from 'path' +import { test, expect } from 'vitest' + +import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' +import { currentDirectory } from '@/utils/file.js' + +import { guardController } from './lib/guardController.js' + +const file = readAOCInputFile({ + filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + type: AOC_OUTPUT_TYPE.STRING_ARRAY_2D +}) as string [][] + +test('Count distinct positions', () => { + expect(guardController(file)).toBe(24) +}) From db1eb28917c14045686f36b175a40be55e32ec02 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 20 Dec 2024 22:14:31 +0800 Subject: [PATCH 05/33] chore: add class notes * ref: use separate type files * chore: create test --- src/2024/2024-12-06/input.txt | 12 ++--- src/2024/2024-12-06/lib/grid.ts | 40 +++++++++++--- src/2024/2024-12-06/lib/grid.types.ts | 10 ++++ src/2024/2024-12-06/lib/guard.ts | 36 ++++--------- src/2024/2024-12-06/lib/guard.types.ts | 62 ++++++++++++++++++++++ src/2024/2024-12-06/lib/guardController.ts | 49 +++++++++-------- src/2024/2024-12-06/main.ts | 6 +-- src/2024/2024-12-06/sample.test.ts | 4 +- 8 files changed, 153 insertions(+), 66 deletions(-) create mode 100644 src/2024/2024-12-06/lib/grid.types.ts create mode 100644 src/2024/2024-12-06/lib/guard.types.ts diff --git a/src/2024/2024-12-06/input.txt b/src/2024/2024-12-06/input.txt index dc1b519..6d7bbf6 100644 --- a/src/2024/2024-12-06/input.txt +++ b/src/2024/2024-12-06/input.txt @@ -1,10 +1,10 @@ ....#..... -.#.......# -....^..... -..#....... -....#..#.. +.........# .......... .#........ -....#...#. -#......... +.......... +.......#.. +.#..^..... +.#......#. +....#..... ......#... \ No newline at end of file diff --git a/src/2024/2024-12-06/lib/grid.ts b/src/2024/2024-12-06/lib/grid.ts index b93148c..38f6019 100644 --- a/src/2024/2024-12-06/lib/grid.ts +++ b/src/2024/2024-12-06/lib/grid.ts @@ -1,24 +1,46 @@ -import { GuardStatus, GuardDirection } from './guard.js' -import type { GuardState } from './guard.js' +import { GuardStatus, GuardDirection } from './guard.types.js' +import type { GuardState } from './guard.types.js' -export type GridDimensions = { - length: number; - width: number; -} +/** + * @class Grid + * @description Object containing a 2D array of strings and other data in which a `Guard` object runs. Each item represents an open path or obstacle. + */ export class Grid { + /** 2D board string array */ board: string[][] = [] + + /** Board length */ length: number = 0 + + /** Board width */ width: number = 0 + + /** Number of distinct positions that a `Guard` can traverse on the board */ positionCount: number = 0 + /** Initial (x,y) coordinate starting position of a `Guard` */ + start: { x: number; y: number; } + + /** Obstruction symbol. Guards turn clockwise if this symbol blocks their next step. */ obstruction = '#' + + /** Path symbol. Guards proceed to the next (x,y) coordinate after on this symbol. */ pathSymbol = '.' - constructor (data: string[][]) { + /** + * @constructor + * @param {string[][]} data 2D string array containing paths `"."`, obstacles `"#"` and the `Guard` object + * @param obstructionSymbol + */ + constructor (data: string[][], obstructionSymbol: string = '#') { this.board = data this.length = data.length this.width = (data[0])?.length as number + this.obstruction = obstructionSymbol + this.start = { x: -1, y: -1 } + + this.findGuardStartPosition() } /** @@ -58,6 +80,9 @@ export class Grid { if (initialState.status === GuardStatus.ACTIVE) break } + this.start.x = initialState.xPos + this.start.y = initialState.yPos + return initialState } @@ -95,6 +120,7 @@ export class Grid { * Displays the guard's distinct positions in the grid */ print () { + console.clear() console.log(this.board.map(row => row.join(' '))) } } diff --git a/src/2024/2024-12-06/lib/grid.types.ts b/src/2024/2024-12-06/lib/grid.types.ts new file mode 100644 index 0000000..4c18219 --- /dev/null +++ b/src/2024/2024-12-06/lib/grid.types.ts @@ -0,0 +1,10 @@ +/** + * `Grid` dimensions properties + * + * @property {number} length - Length of the grid + * @property {number} wwidth - Width of the grid + */ +export type GridDimensions = { + length: number; + width: number; +} diff --git a/src/2024/2024-12-06/lib/guard.ts b/src/2024/2024-12-06/lib/guard.ts index 53d06c2..71e5d2a 100644 --- a/src/2024/2024-12-06/lib/guard.ts +++ b/src/2024/2024-12-06/lib/guard.ts @@ -1,41 +1,23 @@ -export enum GuardDirection { - UP = '^', - RIGHT = '>', - DOWN = 'v', - LEFT = '<' -} - -export enum GuardStatus { - IDLE = 'idle', - ACTIVE = 'active', - EXIT = 'exit' -} - -export type GuardState = { - direction: GuardDirection | null; - xPos: number; - yPos: number; - status: GuardStatus; -} +import type { GuardState } from './guard.types.js' +import { GuardDirection, GuardStatus } from './guard.types.js' /** * @class Guard - * @description Game object that can move and mark its trail in a 2D grid + * @description Game object that can move around in a 2D grid array */ export class Guard { direction: GuardDirection | null = null xPos: number = 0 yPos: number = 0 status: GuardStatus = GuardStatus.IDLE + steps: number = 0 /** + * Creates an instance of the `Guard` class * @constructor * @param {GuardState} state - Initial state of the guard - * @param {GridDimensions} [gridDimensions={ length: 5, width: 5 }] - Dimensions of the grid */ - constructor ( - state: GuardState - ) { + constructor (state: GuardState) { this.direction = state.direction this.xPos = state.xPos || 0 this.yPos = state.yPos || 0 @@ -43,7 +25,8 @@ export class Guard { } /** - * Turn the guard's direction by rotating clockwise once + * Turn the guard's direction clockwise by 90 degrees. + * If the direction is `null`, does nothing. * @returns {void} */ turn (): void { @@ -60,7 +43,7 @@ export class Guard { } /** - * Moves the guard one (1) coordinate [x,y] position in the grid and marks the new position + * Moves the guard by one (1) coordinate [x,y] position in the `Grid` * @param xDirection {number} x coordinate * @param yDirection {number} y coordinate * @returns {void} @@ -70,6 +53,7 @@ export class Guard { this.xPos += xDirection this.yPos += yDirection + this.steps += 1 } /** diff --git a/src/2024/2024-12-06/lib/guard.types.ts b/src/2024/2024-12-06/lib/guard.types.ts new file mode 100644 index 0000000..4106c64 --- /dev/null +++ b/src/2024/2024-12-06/lib/guard.types.ts @@ -0,0 +1,62 @@ +/** + * Directions that a `Guard` can face in a `Grid` + * + * @enum {string} + * @property {string} UP - Upward direction `"^"` + * @property {string} RIGHT - Upward direction `">"` + * @property {string} DOWN - Upward direction `"v"` + * @property {string} LEFT - Upward direction `"<"` + */ +export enum GuardDirection { + UP = '^', + RIGHT = '>', + DOWN = 'v', + LEFT = '<' +} + +/** + * Mapping of `GuardDirections` to their corresponding numeric direction vectors. + * The vector indicates the direction of movement in the `Grid` + * + * @constant + * @type {Record} + * @property {string} [GuardDirection.UP] - Upwards movement direction along the y-axis + * @property {string} [GuardDirection.RIGHT] - Right-ise movement direction along the x-axis + * @property {string} [GuardDirection.DOWN] - Downwards movement direction along the y-axis + * @property {string} [GuardDirection.left] - Left-wise movement direction along the x-axis + */ +export const GuardDirectionVector: Record = { + [GuardDirection.UP]: -1, + [GuardDirection.RIGHT]: 1, + [GuardDirection.DOWN]: 1, + [GuardDirection.LEFT]: -1 +} + +/** + * `Guard` activity status in the `Grid` board + * + * @enum {string} + * @property {string} IDLE - Initial status + * @property {string} ACTIVE - Indicates active placement on the `Grid` + * @property {string} EXIT - Indicates off-Grid placement + */ +export enum GuardStatus { + IDLE = 'idle', + ACTIVE = 'active', + EXIT = 'exit' +} + +/** + * Properties identifying the current state/status of a `Guard` + * + * @property {GuardDirection | null} direction Current direction of a `Guard` in the `Grid` + * @property {number} xPos x-coordinate of the `Guard` in the `Grid` + * @property {number} yPos y-coordinate of the `Guard` in the `Grid` + * @property {GuardStatus} status `Guard` activity status + */ +export type GuardState = { + direction: GuardDirection | null; + xPos: number; + yPos: number; + status: GuardStatus; +} diff --git a/src/2024/2024-12-06/lib/guardController.ts b/src/2024/2024-12-06/lib/guardController.ts index 50eea24..482fb9f 100644 --- a/src/2024/2024-12-06/lib/guardController.ts +++ b/src/2024/2024-12-06/lib/guardController.ts @@ -1,17 +1,15 @@ - - -import { Guard, GuardDirection } from './guard.js' +import { GuardDirection, GuardStatus } from './guard.types.js' +import { Guard } from './guard.js' import { Grid } from './grid.js' -import { GuardStatus } from './guard.js' /** - * + * Runs the `Guard` on the grid, counting distinct positions/steps * @param {string[][]} data 2D string array containing the guards grid paths * @param {boolean} printTrail Flag to display the distinct guard positions in the grid * @returns {number} Number of unique guard positions */ -export const guardController = (data: string[][], printTrail: boolean = false): number => { - const grid = new Grid(data) +export const guardController = (data: string[][], printTrail: boolean = false): Grid => { + const grid = new Grid(structuredClone(data)) const guard = new Guard(grid.findGuardStartPosition()) while ( @@ -21,6 +19,7 @@ export const guardController = (data: string[][], printTrail: boolean = false): let yDirection = 0 let xDirection = 0 + // Find the guard's direction switch(guard.direction) { case GuardDirection.UP: yDirection = -1 @@ -34,26 +33,32 @@ export const guardController = (data: string[][], printTrail: boolean = false): case GuardDirection.RIGHT: xDirection = 1 break + default: + break + } + + if ( + guard.yPos === grid.board.length - 1 && + guard.xPos === (grid.board[0] as string[])?.length - 1 + ) { + // Exit if the coordinates are at the edge of the Grid + guard.exit() } - if (grid.board[guard.yPos + yDirection]?.[guard.xPos + xDirection] !== grid.obstruction) { - if ( - guard.yPos === grid.board.length - 1 && - guard.xPos === (grid.board[0] as string[])?.length - 1 - ) { - guard.exit() - } else { - grid.mark(guard.xPos, guard.yPos) - guard.walk(xDirection, yDirection) - } + const xAhead = guard.xPos + xDirection + const yAhead = guard.yPos + yDirection + const symbol = grid.board[yAhead]?.[xAhead] + + if (symbol !== grid.obstruction) { + // Walk and mark the current coordinates + grid.mark(guard.xPos, guard.yPos) + guard.walk(xDirection, yDirection) } else { + // Turn (rotate clockwise) if the symbol ahead is an obstruction `"#" guard.turn() } } - if (printTrail) { - grid.print() - } - - return grid.positionCount + if (printTrail) grid.print() + return grid } diff --git a/src/2024/2024-12-06/main.ts b/src/2024/2024-12-06/main.ts index 6aeaf60..a5d1e37 100644 --- a/src/2024/2024-12-06/main.ts +++ b/src/2024/2024-12-06/main.ts @@ -14,10 +14,10 @@ const file = readAOCInputFile({ * Counts the number of distinct guard positions in a grid */ export const quiz20241206_01 = () => { - const positionCount = guardController(file, true) + const grid = guardController(file, true) - console.log('Distinct guard positions', positionCount) - return positionCount + console.log('Distinct guard positions:', grid.positionCount) + return grid.positionCount } quiz20241206_01() diff --git a/src/2024/2024-12-06/sample.test.ts b/src/2024/2024-12-06/sample.test.ts index 8ed908e..d0b6e69 100644 --- a/src/2024/2024-12-06/sample.test.ts +++ b/src/2024/2024-12-06/sample.test.ts @@ -11,6 +11,6 @@ const file = readAOCInputFile({ type: AOC_OUTPUT_TYPE.STRING_ARRAY_2D }) as string [][] -test('Count distinct positions', () => { - expect(guardController(file)).toBe(24) +test('Count distinct guard positions', () => { + expect(guardController(file).positionCount).toBe(26) }) From 71999a5c31cee80b1f1d3407de60a6262ae3964e Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 20 Dec 2024 22:34:31 +0800 Subject: [PATCH 06/33] feat: day 6 guard gallivant quiz 2/2 soln, #11 --- src/2024/2024-12-06/README.md | 21 ++- .../2024-12-06/lib/guardControllerLoop.ts | 124 ++++++++++++++++++ src/2024/2024-12-06/main.ts | 14 ++ src/2024/2024-12-06/sample.test.ts | 5 + 4 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 src/2024/2024-12-06/lib/guardControllerLoop.ts diff --git a/src/2024/2024-12-06/README.md b/src/2024/2024-12-06/README.md index e3da586..eb3a023 100644 --- a/src/2024/2024-12-06/README.md +++ b/src/2024/2024-12-06/README.md @@ -3,4 +3,23 @@ Visit the Advent of Code website for more information on this puzzle at: **Source:** https://adventofcode.com/2024/day/6
-**Status:** On-going ⭐ +**Status:** Complete ⭐⭐ + +## Code + +1. **grid.ts** + - `Grid` - class that has a 2D array object containing paths and obstacles in which a `Guard` runs. + - Has functions and methods for managing a `Grid` object + +2. **guard.ts** + - `Guard` - class representing an object that can walk in the `Grid` map. + - Have methods for moving around in the `Grid`. + +3. **guardController.ts** + - `guardController()` - Runs the `Guard` on the `Grid`, counting distinct positions/steps. + +5. **guardControllerLoop.ts** + - `gridHasInfiniteLoop()` + - Runs the `Guard` on a `Grid` with obstacles, checking the placement of paths and obstacles that will make the `Guard` walk in an infinite loop + - `findObstructionPositions()` - Counts the number of positions in the `Grid` in which inserting one (1) obstruction symbol # will cause the `Guard` to walk in an infinite loop. + - > _**WARNING:** This is a very slow, unoptimized program execution that takes about ~3 mins to complete using the actual AoC input text._ diff --git a/src/2024/2024-12-06/lib/guardControllerLoop.ts b/src/2024/2024-12-06/lib/guardControllerLoop.ts new file mode 100644 index 0000000..d25429f --- /dev/null +++ b/src/2024/2024-12-06/lib/guardControllerLoop.ts @@ -0,0 +1,124 @@ +import { GuardDirection, GuardStatus } from './guard.types.js' +import { Guard } from './guard.js' +import { Grid } from './grid.js' +import { guardController } from './guardController.js' + +/** + * Runs the `Guard` on a `Grid` with obstacles, checking the placement of paths and obstacles that will make the `Guard` walk in an infinite loop + * @param {string[][]} data 2D string array containing the original `Grid` inserted with a new obstruction symbol `"#" + * @param {boolean} printTrail Flag to display the distinct guard positions in the grid + * @returns {number} Number of unique guard positions + */ +export const gridHasInfiniteLoop = ( + data: string[][], + printTrail: boolean = false +): boolean => { + const grid = new Grid(data, '#') + const initialPosition = grid.findGuardStartPosition() + + const guard = new Guard({ + ...initialPosition, + yPos: initialPosition.yPos - 1 + }) + + const trail = [] + let isLoop = false + + while ( + guard.status !== GuardStatus.EXIT && + !grid.isOutOfBounds(guard.xPos, guard.yPos) && + !isLoop + ) { + let yDirection = 0 + let xDirection = 0 + + // Find the guard's direction + switch(guard.direction) { + case GuardDirection.UP: + yDirection = -1 + break + case GuardDirection.DOWN: + yDirection = 1 + break + case GuardDirection.LEFT: + xDirection = -1 + break + case GuardDirection.RIGHT: + xDirection = 1 + break + default: + break + } + + if ( + guard.yPos === grid.board.length - 1 && + guard.xPos === (grid.board[0] as string[])?.length - 1 + ) { + // Exit if the coordinates are at the edge of the Grid + guard.exit() + } + + const xAhead = guard.xPos + xDirection + const yAhead = guard.yPos + yDirection + const symbol = grid.board[yAhead]?.[xAhead] + + if (symbol !== grid.obstruction) { + const coord = `${guard.xPos},${guard.yPos},${guard.direction}` + + if (trail.indexOf(coord) >= 0) { + // If the coordinates and direction repeat, + // Guard has already entered an infinite loop + isLoop = true + } else { + // Note the (x,y) coordinates and direction + trail.push(coord) + } + + grid.mark(guard.xPos, guard.yPos) + guard.walk(xDirection, yDirection) + } else { + // Turn (rotate clockwise) if the symbol ahead is an obstruction `"#" + guard.turn() + } + } + + if (printTrail) grid.print() + return isLoop +} + +/** + * Counts the number of positions in the `Grid` in which inserting one (1) + * obstruction symbol # will cause the `Guard` to walk in an infinite loop. + * @param {number} data data 2D string array containing the `Grid` paths + */ +export const findObstructionPositions = (data: string[][], printGrid: boolean = false) => { + let loopCount = 0 + const paths: Grid = guardController(data) + const extras = Array.from({ length: data.length }, () => Array(data.length).fill('.')) + + for (let y = 0; y < paths.board.length; y += 1) { + const row = paths.board[y] as string[] + + for (let x = 0; x < row.length; x += 1) { + if (row[x] !== 'X') continue + if (x === paths.start.x && y === paths.start.y) { + continue + } + + const xData: string[][] = structuredClone(data) + const r = xData[y] as string[] + r[x] = '#' + + const isLoop = gridHasInfiniteLoop(xData) + + if (isLoop) { + const e = extras[y] as string[] + e[x] = 'O' + loopCount += 1 + } + } + } + + if (printGrid) console.log(extras.map(x => x.join(' '))) + return loopCount +} diff --git a/src/2024/2024-12-06/main.ts b/src/2024/2024-12-06/main.ts index a5d1e37..fdd1040 100644 --- a/src/2024/2024-12-06/main.ts +++ b/src/2024/2024-12-06/main.ts @@ -3,6 +3,7 @@ import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' import { currentDirectory } from '@/utils/file.js' import { guardController } from './lib/guardController.js' +import { findObstructionPositions } from './lib/guardControllerLoop.js' const file = readAOCInputFile({ filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), @@ -20,4 +21,17 @@ export const quiz20241206_01 = () => { return grid.positionCount } +/** + * Part 2/2 of the 2024-12-06 quiz + * Counts the number of positions in the Grid in which placing an + * obstacle will cause the Guard to walk in an infinite loop + */ +export const quiz20241206_02 = () => { + const infinitePositions = findObstructionPositions(file) + + console.log('Obstruction positions for infinite walk:', infinitePositions) + return infinitePositions +} + quiz20241206_01() +quiz20241206_02() diff --git a/src/2024/2024-12-06/sample.test.ts b/src/2024/2024-12-06/sample.test.ts index d0b6e69..b46d07d 100644 --- a/src/2024/2024-12-06/sample.test.ts +++ b/src/2024/2024-12-06/sample.test.ts @@ -5,6 +5,7 @@ import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' import { currentDirectory } from '@/utils/file.js' import { guardController } from './lib/guardController.js' +import { findObstructionPositions } from './lib/guardControllerLoop.js' const file = readAOCInputFile({ filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), @@ -14,3 +15,7 @@ const file = readAOCInputFile({ test('Count distinct guard positions', () => { expect(guardController(file).positionCount).toBe(26) }) + +test('Count obstackle positions', () => { + expect(findObstructionPositions(file)).toBe(2) +}) From 2a8543384930f561125c496f3993deb3676d6ed8 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 20 Dec 2024 22:41:55 +0800 Subject: [PATCH 07/33] chore: update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a92f47c..a0b0053 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ This repository contains solutions and a local development environment for the [ - Day 3: Mull It Over [[link]](/src/2024/2024-12-03/README.md) - Day 4: Ceres Search [[link]](/src/2024/2024-12-04/README.md) - Day 5: Print Queue [[link]](/src/2024/2024-12-05/README.md) +- Day 6: Guard Gallivant [[link]](/src/2024/2024-12-06/README.md) From ec8e6bba7dba83ac8a26f929a574ba6843cdc35a Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 22 Dec 2024 02:44:18 +0800 Subject: [PATCH 08/33] feat: day 7 bridge repair 1/2 soln, #12 --- src/2024/2024-12-06/lib/guardController.ts | 2 +- .../2024-12-06/lib/guardControllerLoop.ts | 2 +- src/2024/2024-12-06/sample.test.ts | 2 +- src/2024/2024-12-07/README.md | 15 ++++ src/2024/2024-12-07/input.txt | 9 ++ src/2024/2024-12-07/lib/totalCalibration.ts | 84 +++++++++++++++++++ src/2024/2024-12-07/main.ts | 22 +++++ src/2024/2024-12-07/sample.test.ts | 18 ++++ src/utils/aocInputFile.ts | 1 - 9 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 src/2024/2024-12-07/README.md create mode 100644 src/2024/2024-12-07/input.txt create mode 100644 src/2024/2024-12-07/lib/totalCalibration.ts create mode 100644 src/2024/2024-12-07/main.ts create mode 100644 src/2024/2024-12-07/sample.test.ts diff --git a/src/2024/2024-12-06/lib/guardController.ts b/src/2024/2024-12-06/lib/guardController.ts index 482fb9f..028474f 100644 --- a/src/2024/2024-12-06/lib/guardController.ts +++ b/src/2024/2024-12-06/lib/guardController.ts @@ -19,7 +19,7 @@ export const guardController = (data: string[][], printTrail: boolean = false): let yDirection = 0 let xDirection = 0 - // Find the guard's direction + // Set the guard's direction in the grid switch(guard.direction) { case GuardDirection.UP: yDirection = -1 diff --git a/src/2024/2024-12-06/lib/guardControllerLoop.ts b/src/2024/2024-12-06/lib/guardControllerLoop.ts index d25429f..8463ce5 100644 --- a/src/2024/2024-12-06/lib/guardControllerLoop.ts +++ b/src/2024/2024-12-06/lib/guardControllerLoop.ts @@ -32,7 +32,7 @@ export const gridHasInfiniteLoop = ( let yDirection = 0 let xDirection = 0 - // Find the guard's direction + // Set the guard's direction in the grid switch(guard.direction) { case GuardDirection.UP: yDirection = -1 diff --git a/src/2024/2024-12-06/sample.test.ts b/src/2024/2024-12-06/sample.test.ts index b46d07d..aff13c1 100644 --- a/src/2024/2024-12-06/sample.test.ts +++ b/src/2024/2024-12-06/sample.test.ts @@ -16,6 +16,6 @@ test('Count distinct guard positions', () => { expect(guardController(file).positionCount).toBe(26) }) -test('Count obstackle positions', () => { +test('Count obstacle positions', () => { expect(findObstructionPositions(file)).toBe(2) }) diff --git a/src/2024/2024-12-07/README.md b/src/2024/2024-12-07/README.md new file mode 100644 index 0000000..8104bf6 --- /dev/null +++ b/src/2024/2024-12-07/README.md @@ -0,0 +1,15 @@ +## Day 7: Bridge Repair + +Visit the Advent of Code website for more information on this puzzle at: + +**Source:** https://adventofcode.com/2024/day/7
+**Status:** On-going ⭐ + +## Code + +1. **totalCalibration.ts** + - `operatorCombinations()` - Finds the total number of possible combinations in which to place operator symbols for an `N`-length array + + - `doEquation()` - Returns the result of an AoC text equation and a set of operator symbols with the condition: left to write equation processing, disregarding operator precedence + + - `totalCalibrationResult()` - Counts the total calibration sum of input lines whose elements (numbers) match the line's target sum after processing with one of N possible combinations of `+` and `*` operands \ No newline at end of file diff --git a/src/2024/2024-12-07/input.txt b/src/2024/2024-12-07/input.txt new file mode 100644 index 0000000..7ece8aa --- /dev/null +++ b/src/2024/2024-12-07/input.txt @@ -0,0 +1,9 @@ +190: 10 19 +3267: 81 40 27 +83: 17 5 +156: 15 6 +7290: 6 8 6 15 +161011: 16 10 13 +192: 17 8 14 +21037: 9 7 18 13 +292: 11 6 16 20 \ No newline at end of file diff --git a/src/2024/2024-12-07/lib/totalCalibration.ts b/src/2024/2024-12-07/lib/totalCalibration.ts new file mode 100644 index 0000000..accfaab --- /dev/null +++ b/src/2024/2024-12-07/lib/totalCalibration.ts @@ -0,0 +1,84 @@ +/** + * Finds the total number of possible combinations in which to place operator symbols for an `N`-length array + * @param {string[]} operators String array containing operator symbols + * @param {number} N Length of a linear array + * @returns {number} Total number + */ +const operatorCombinations = ( + operators: string[] = ['+', '*'], + N: number +) => { + const combinations: string[][] = [] + const totalCombinations = Math.pow(operators.length, N) + + for (let i = 0; i < totalCombinations; i += 1) { + const list: string[] = [] + let seed = i + + for (let j = 0; j < N; j += 1) { + list.push(operators[seed % operators.length] as string) + seed = Math.floor(seed / operators.length) + } + + combinations.push(list) + } + + return combinations +} + +/** + * Returns the result of an AoC text equation and a set of operator symbols with the condition: + * - Left to write equation processing, disregarding operator precedence + * @param {string} eqnString Math equation expressed as string + * @returns {number} Result of the `eqnString` + */ +const doEquation = (numbers: number[], operators: string[]): number => { + let sum = numbers[0] as number + + operators.forEach((operator, index) => { + if (operator === '+') { + sum += numbers[index + 1] as number + } + + if (operator === '*') { + sum *= numbers[index + 1] as number + } + }) + + return sum +} + +/** + * Counts the total calibration sum of input lines whose elements (numbers) match the line's target sum + * after processing with one of N possible combinations of `+` and `*` operands + * @param input Input string array + * @returns {number} Total calibration sum + */ +export const totalCalibrationResult = (input: string[]): number => { + const operators = ['+', '*'] + let sum = 0 + + for (let i = 0; i < input.length; i += 1) { + const line = input[i] + + if (line === undefined) break + const [targetSum, data] = line.split(': ') + const numbers = data?.split(' ').map(Number) as number[] + + // Find all operator placement combinations + const combinations = operatorCombinations(operators, numbers.length - 1) + + // Build the text equation + for (let j = 0; j < combinations.length; j += 1) { + // Process equation + const result = doEquation(numbers, combinations[j] as string[]) + + if (result === Number(targetSum)) { + sum += result + break + } + } + } + + return sum +} diff --git a/src/2024/2024-12-07/main.ts b/src/2024/2024-12-07/main.ts new file mode 100644 index 0000000..b84acb4 --- /dev/null +++ b/src/2024/2024-12-07/main.ts @@ -0,0 +1,22 @@ +import path from 'path' +import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' +import { currentDirectory } from '@/utils/file.js' + +import { totalCalibrationResult } from './lib/totalCalibration.js' + +// Read and process the input file +const input = (readAOCInputFile({ + filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + type: AOC_OUTPUT_TYPE.STRING +}) as string) + .split('\n') + +/** + * Part 1/2 of the 2024-12-07 quiz + * Counts the total calibration result of the input data + */ +const quiz20241221_01 = () => { + totalCalibrationResult(input) +} + +quiz20241221_01() diff --git a/src/2024/2024-12-07/sample.test.ts b/src/2024/2024-12-07/sample.test.ts new file mode 100644 index 0000000..23213e8 --- /dev/null +++ b/src/2024/2024-12-07/sample.test.ts @@ -0,0 +1,18 @@ +import path from 'path' +import { test, expect } from 'vitest' + +import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' +import { currentDirectory } from '@/utils/file.js' + +import { totalCalibrationResult } from './lib/totalCalibration.js' + +// Read and process the input file +const input = (readAOCInputFile({ + filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + type: AOC_OUTPUT_TYPE.STRING +}) as string) + .split('\n') + +test('1/2: Total calibration result', () => { + expect(totalCalibrationResult(input)).toBe(3749) +}) diff --git a/src/utils/aocInputFile.ts b/src/utils/aocInputFile.ts index 4a0fb5a..56e99f2 100644 --- a/src/utils/aocInputFile.ts +++ b/src/utils/aocInputFile.ts @@ -25,7 +25,6 @@ type Output = string | string[] | string[][] | * @param param.filePath {string} Full file path to an input text file * @param param.type {AOC_OUTPUT_TYPE} Type to convert the input text file * @param param.delimiter {string} String delimiter - * @param moduleFile {string} File URL of the current module being executed: `"import.meta.url"` * @throws {Error} */ export const readAOCInputFile = (param: FileInput): Output => { From 99c10a01ffae4ce48d3a95f8935a7d74c1ef6c5b Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 22 Dec 2024 02:50:12 +0800 Subject: [PATCH 09/33] chore: use random input --- src/2024/2024-12-07/input.txt | 17 ++++++++--------- src/2024/2024-12-07/sample.test.ts | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/2024/2024-12-07/input.txt b/src/2024/2024-12-07/input.txt index 7ece8aa..95d79a9 100644 --- a/src/2024/2024-12-07/input.txt +++ b/src/2024/2024-12-07/input.txt @@ -1,9 +1,8 @@ -190: 10 19 -3267: 81 40 27 -83: 17 5 -156: 15 6 -7290: 6 8 6 15 -161011: 16 10 13 -192: 17 8 14 -21037: 9 7 18 13 -292: 11 6 16 20 \ No newline at end of file +180: 10 18 +3124: 64 44 19 +76: 21 4 +194: 3 5 +1432: 1 1 1 3 +15: 5 5 2 3 +12345: 6 4 9 10 +100: 25 50 10 15 \ No newline at end of file diff --git a/src/2024/2024-12-07/sample.test.ts b/src/2024/2024-12-07/sample.test.ts index 23213e8..f4ab685 100644 --- a/src/2024/2024-12-07/sample.test.ts +++ b/src/2024/2024-12-07/sample.test.ts @@ -14,5 +14,5 @@ const input = (readAOCInputFile({ .split('\n') test('1/2: Total calibration result', () => { - expect(totalCalibrationResult(input)).toBe(3749) + expect(totalCalibrationResult(input)).toBe(295) }) From 376b3ba14d6300f73fe6836e05b65753ae3aa6d0 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 22 Dec 2024 03:12:24 +0800 Subject: [PATCH 10/33] chore: update note --- .gitignore | 1 + src/2024/2024-12-07/README.md | 2 +- src/2024/2024-12-07/lib/totalCalibration.ts | 7 ++++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index d19df24..5db6565 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist/ *.zip *.rar *.tgz +src/**/*.js diff --git a/src/2024/2024-12-07/README.md b/src/2024/2024-12-07/README.md index 8104bf6..5aa3e96 100644 --- a/src/2024/2024-12-07/README.md +++ b/src/2024/2024-12-07/README.md @@ -8,7 +8,7 @@ Visit the Advent of Code website for more information on this puzzle at: ## Code 1. **totalCalibration.ts** - - `operatorCombinations()` - Finds the total number of possible combinations in which to place operator symbols for an `N`-length array + - `operatorCombinations()` - Finds the possible combinations in which to place operator symbols for an `N`-length array - `doEquation()` - Returns the result of an AoC text equation and a set of operator symbols with the condition: left to write equation processing, disregarding operator precedence diff --git a/src/2024/2024-12-07/lib/totalCalibration.ts b/src/2024/2024-12-07/lib/totalCalibration.ts index accfaab..a7f7cfb 100644 --- a/src/2024/2024-12-07/lib/totalCalibration.ts +++ b/src/2024/2024-12-07/lib/totalCalibration.ts @@ -1,13 +1,13 @@ /** - * Finds the total number of possible combinations in which to place operator symbols for an `N`-length array + * Finds the possible combinations in which to place operator symbols for an `N`-length array * @param {string[]} operators String array containing operator symbols * @param {number} N Length of a linear array - * @returns {number} Total number + * @returns {string[][]} List (2D-string array) of possible operators placement combinations */ const operatorCombinations = ( operators: string[] = ['+', '*'], N: number -) => { +): string[][] => { const combinations: string[][] = [] const totalCombinations = Math.pow(operators.length, N) @@ -62,6 +62,7 @@ export const totalCalibrationResult = (input: string[]): number => { const line = input[i] if (line === undefined) break + const [targetSum, data] = line.split(': ') const numbers = data?.split(' ').map(Number) as number[] From 943486f8bd09d9cdcaf99c0aa3d1606af4463556 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 22 Dec 2024 07:16:59 +0800 Subject: [PATCH 11/33] feat: day 7 bridge repair 2/2 soln, #12 --- input.txt | 9 +++ src/2024/2024-12-07/README.md | 24 +++++-- src/2024/2024-12-07/input.txt | 3 +- src/2024/2024-12-07/lib/totalCalibration.ts | 6 +- .../2024-12-07/lib/totalCalibrationConcat.ts | 70 +++++++++++++++++++ src/2024/2024-12-07/main.ts | 19 ++++- src/2024/2024-12-07/sample.test.ts | 5 ++ 7 files changed, 124 insertions(+), 12 deletions(-) create mode 100644 input.txt create mode 100644 src/2024/2024-12-07/lib/totalCalibrationConcat.ts diff --git a/input.txt b/input.txt new file mode 100644 index 0000000..7ece8aa --- /dev/null +++ b/input.txt @@ -0,0 +1,9 @@ +190: 10 19 +3267: 81 40 27 +83: 17 5 +156: 15 6 +7290: 6 8 6 15 +161011: 16 10 13 +192: 17 8 14 +21037: 9 7 18 13 +292: 11 6 16 20 \ No newline at end of file diff --git a/src/2024/2024-12-07/README.md b/src/2024/2024-12-07/README.md index 5aa3e96..b32d271 100644 --- a/src/2024/2024-12-07/README.md +++ b/src/2024/2024-12-07/README.md @@ -3,13 +3,27 @@ Visit the Advent of Code website for more information on this puzzle at: **Source:** https://adventofcode.com/2024/day/7
-**Status:** On-going ⭐ +**Status:** Complete ⭐⭐ ## Code -1. **totalCalibration.ts** - - `operatorCombinations()` - Finds the possible combinations in which to place operator symbols for an `N`-length array +### `totalCalibration.ts` - - `doEquation()` - Returns the result of an AoC text equation and a set of operator symbols with the condition: left to write equation processing, disregarding operator precedence +- `operatorCombinations()` + - Finds the possible combinations in which to place operator symbols for an `N`-length array - - `totalCalibrationResult()` - Counts the total calibration sum of input lines whose elements (numbers) match the line's target sum after processing with one of N possible combinations of `+` and `*` operands \ No newline at end of file +- `doEquation()` + - Returns the result of an AoC text equation and a set of operator symbols with the condition: left to write equation processing, disregarding operator precedence + +- `totalCalibrationResult()` + - Counts the total calibration sum of input lines whose elements (numbers) match the line's target sum after processing with one of N possible combinations of `+` and `*` operator placements + +### `totalCalibrationConcat.ts` + +- `doEquationWithConcat()` + - Returns the result of an AoC text equation and a set of operator symbols with the conditions: + - Concatenate two (2) numbers with the `||` symbol in between + - Left to write equation processing, disregarding operator precedence + +- `totalCalibrationConcat()` + - Counts the total calibration sum of input lines whose single or concatenated "joined" elements (numbers) match the line's target sum after processing with one of N possible combinations of `+`, `*` and `||` (concatenator) operator placements \ No newline at end of file diff --git a/src/2024/2024-12-07/input.txt b/src/2024/2024-12-07/input.txt index 95d79a9..a7efc4b 100644 --- a/src/2024/2024-12-07/input.txt +++ b/src/2024/2024-12-07/input.txt @@ -5,4 +5,5 @@ 1432: 1 1 1 3 15: 5 5 2 3 12345: 6 4 9 10 -100: 25 50 10 15 \ No newline at end of file +100: 25 50 10 15 +125: 12 5 \ No newline at end of file diff --git a/src/2024/2024-12-07/lib/totalCalibration.ts b/src/2024/2024-12-07/lib/totalCalibration.ts index a7f7cfb..4c97e46 100644 --- a/src/2024/2024-12-07/lib/totalCalibration.ts +++ b/src/2024/2024-12-07/lib/totalCalibration.ts @@ -4,7 +4,7 @@ * @param {number} N Length of a linear array * @returns {string[][]} List (2D-string array) of possible operators placement combinations */ -const operatorCombinations = ( +export const operatorCombinations = ( operators: string[] = ['+', '*'], N: number ): string[][] => { @@ -32,7 +32,7 @@ const operatorCombinations = ( * @param {string} eqnString Math equation expressed as string * @returns {number} Result of the `eqnString` */ -const doEquation = (numbers: number[], operators: string[]): number => { +export const doEquation = (numbers: number[], operators: string[]): number => { let sum = numbers[0] as number operators.forEach((operator, index) => { @@ -50,7 +50,7 @@ const doEquation = (numbers: number[], operators: string[]): number => { /** * Counts the total calibration sum of input lines whose elements (numbers) match the line's target sum - * after processing with one of N possible combinations of `+` and `*` operands + * after processing with one of N possible combinations of `+` and `*` operator placements * @param input Input string array * @returns {number} Total calibration sum */ diff --git a/src/2024/2024-12-07/lib/totalCalibrationConcat.ts b/src/2024/2024-12-07/lib/totalCalibrationConcat.ts new file mode 100644 index 0000000..b8a6cb8 --- /dev/null +++ b/src/2024/2024-12-07/lib/totalCalibrationConcat.ts @@ -0,0 +1,70 @@ +import { operatorCombinations } from './totalCalibration.js' + +/** + * Returns the result of an AoC text equation and a set of operator symbols with the condition: + * - Concatenate two (2) numbers with the `||` symbol in between + * - Left to write equation processing, disregarding operator precedence + * @param {string} eqnString Math equation expressed as string + * @returns {number} Result of the `eqnString` + */ +const doEquationWithConcat = (numbers: number[], operators: string[]): number => { + let sum = numbers[0] as number + + operators.forEach((operator, index) => { + if (operator === '+') { + sum += numbers[index + 1] as number + } + + if (operator === '*') { + sum *= numbers[index + 1] as number + } + + if (operator === '||') { + const concatNum = `${sum}${numbers[index + 1]}` + sum = Number(concatNum) + } + }) + + return sum +} + +/** + * Counts the total calibration sum of input lines whose single or concatenated "joined" elements (numbers) + * match the line's target sum after processing with one of N possible combinations + * of `+`, `*` and `||` (concatenator) operator placements + * @param input Input string array + * @returns {number} Total calibration sum + */ +export const totalCalibrationConcat = (input: string[]): number => { + let sum = 0 + + for (let i = 0; i < input.length; i += 1) { + const line = input[i] + + if (line === undefined) break + + const [targetSum, data] = line.split(': ') + const numbers = data?.split(' ').map(Number) as number[] + + // Find all operator placement combinations + const combinations = operatorCombinations( + ['+', '*', '||'], + numbers.length - 1 + ) + + // Process the text equations + for (let j = 0; j < combinations.length; j += 1) { + const result = doEquationWithConcat( + numbers, + combinations[j] as string[] + ) + + if (result === Number(targetSum)) { + sum += result + break + } + } + } + + return sum +} diff --git a/src/2024/2024-12-07/main.ts b/src/2024/2024-12-07/main.ts index b84acb4..56e619e 100644 --- a/src/2024/2024-12-07/main.ts +++ b/src/2024/2024-12-07/main.ts @@ -3,6 +3,7 @@ import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' import { currentDirectory } from '@/utils/file.js' import { totalCalibrationResult } from './lib/totalCalibration.js' +import { totalCalibrationConcat } from './lib/totalCalibrationConcat.js' // Read and process the input file const input = (readAOCInputFile({ @@ -15,8 +16,20 @@ const input = (readAOCInputFile({ * Part 1/2 of the 2024-12-07 quiz * Counts the total calibration result of the input data */ -const quiz20241221_01 = () => { - totalCalibrationResult(input) +const quiz20241207_01 = () => { + const total = totalCalibrationResult(input) + console.log('Calibration total:', total) } -quiz20241221_01() +/** + * Part 2/2 of the 2024-12-07 quiz + * Counts the total calibration result of the input data including + * "concatenated" numbers + */ +const quiz20241207_02 = () => { + const total = totalCalibrationConcat(input) + console.log('Calibration total (with concat):', total) +} + +quiz20241207_01() +quiz20241207_02() diff --git a/src/2024/2024-12-07/sample.test.ts b/src/2024/2024-12-07/sample.test.ts index f4ab685..4872808 100644 --- a/src/2024/2024-12-07/sample.test.ts +++ b/src/2024/2024-12-07/sample.test.ts @@ -5,6 +5,7 @@ import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' import { currentDirectory } from '@/utils/file.js' import { totalCalibrationResult } from './lib/totalCalibration.js' +import { totalCalibrationConcat } from './lib/totalCalibrationConcat.js' // Read and process the input file const input = (readAOCInputFile({ @@ -16,3 +17,7 @@ const input = (readAOCInputFile({ test('1/2: Total calibration result', () => { expect(totalCalibrationResult(input)).toBe(295) }) + +test('2/2: Total calibration result (with concat)', () => { + expect(totalCalibrationConcat(input)).toBe(420) +}) From 45d3a5f57557d09a57316388f263f23a38994200 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 22 Dec 2024 07:20:03 +0800 Subject: [PATCH 12/33] chore: ignore text files in root dir --- .gitignore | 3 +++ input.txt | 9 --------- 2 files changed, 3 insertions(+), 9 deletions(-) delete mode 100644 input.txt diff --git a/.gitignore b/.gitignore index 5db6565..38e27fe 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ dist/ *.rar *.tgz src/**/*.js + +**/*.txt +!src/**/*.txt diff --git a/input.txt b/input.txt deleted file mode 100644 index 7ece8aa..0000000 --- a/input.txt +++ /dev/null @@ -1,9 +0,0 @@ -190: 10 19 -3267: 81 40 27 -83: 17 5 -156: 15 6 -7290: 6 8 6 15 -161011: 16 10 13 -192: 17 8 14 -21037: 9 7 18 13 -292: 11 6 16 20 \ No newline at end of file From 6582fb30da4f715e7bf87783cbde09e6366cd3ce Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 22 Dec 2024 07:21:42 +0800 Subject: [PATCH 13/33] chore: update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a0b0053..5ecdb51 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ This repository contains solutions and a local development environment for the [ - Day 4: Ceres Search [[link]](/src/2024/2024-12-04/README.md) - Day 5: Print Queue [[link]](/src/2024/2024-12-05/README.md) - Day 6: Guard Gallivant [[link]](/src/2024/2024-12-06/README.md) +- Day 7: Bridge Repair [[link]](/src/2024/2024-12-07/README.md) From c81121db514dd7b85743048782bba3da1ed65f86 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Wed, 25 Dec 2024 05:46:27 +0800 Subject: [PATCH 14/33] feat: day 8 resonant collinearity 1/2 soln, #13 --- README.md | 6 +- src/2024/2024-12-08/README.md | 16 ++++ src/2024/2024-12-08/input.txt | 12 +++ src/2024/2024-12-08/lib/GridAntinodes.ts | 99 ++++++++++++++++++++++ src/2024/2024-12-08/lib/types.ts | 8 ++ src/2024/2024-12-08/lib/uniqueAntinodes.ts | 60 +++++++++++++ src/2024/2024-12-08/main.ts | 21 +++++ src/2024/2024-12-08/sample.test.ts | 16 ++++ 8 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 src/2024/2024-12-08/README.md create mode 100644 src/2024/2024-12-08/input.txt create mode 100644 src/2024/2024-12-08/lib/GridAntinodes.ts create mode 100644 src/2024/2024-12-08/lib/types.ts create mode 100644 src/2024/2024-12-08/lib/uniqueAntinodes.ts create mode 100644 src/2024/2024-12-08/main.ts create mode 100644 src/2024/2024-12-08/sample.test.ts diff --git a/README.md b/README.md index 5ecdb51..7f41a4c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ ## ✨ adventofcode -This repository contains solutions and a local development environment for the [Advent of Code](https://adventofcode.com/) event puzzles. +This repository contains solutions and a local development environment for the [Advent of Code](https://adventofcode.com/) event puzzles using TypeScript/JavaScript. + +The codes are structured in a way that discusses and walks through the solution steps for the AoC quizzes rather than focusing on AoC's competitive programming. ### 🎄 Advent of Code Quiz Information @@ -110,7 +112,7 @@ Using Node npm run transpile node dist/sample/sample.js ``` -4. See the [Available Scripts](#available-scripts) section for more information. +4. See the [Available Scripts](#-available-scripts) section for more information. ## ⚡ Alternate Usage diff --git a/src/2024/2024-12-08/README.md b/src/2024/2024-12-08/README.md new file mode 100644 index 0000000..f547952 --- /dev/null +++ b/src/2024/2024-12-08/README.md @@ -0,0 +1,16 @@ +## Day 8: Resonant Collinearity + +Visit the Advent of Code website for more information on this puzzle at: + +**Source:** https://adventofcode.com/2024/day/8
+**Status:** On-going ⭐ + +## Code + +### `GridAntinodes.ts` + +- `GridAntiNodes` class - Object that tracks and manages `Antennas` and `Antinodes` in a 2D grid array + +### `uniqueAntinodes.ts` + +- `uniqueAntinodes()` - Counts the unique locations in the grid that contains an antinode diff --git a/src/2024/2024-12-08/input.txt b/src/2024/2024-12-08/input.txt new file mode 100644 index 0000000..1ed83fc --- /dev/null +++ b/src/2024/2024-12-08/input.txt @@ -0,0 +1,12 @@ +............ +........x... +.....x...... +.......x.... +....x....... +......B..... +............ +............ +........B... +.........B.. +............ +............ \ No newline at end of file diff --git a/src/2024/2024-12-08/lib/GridAntinodes.ts b/src/2024/2024-12-08/lib/GridAntinodes.ts new file mode 100644 index 0000000..482d7be --- /dev/null +++ b/src/2024/2024-12-08/lib/GridAntinodes.ts @@ -0,0 +1,99 @@ +import type { Antenna } from './types.js' + +/** + * @class GridAntiNodes + * @description Object that tracks `Antennas` and `Antinodes` in a 2D grid array + */ +export class GridAntiNodes { + /** 2D string user input array */ + board: string[][] = [] + + /** Array containing Antenna (x,y) coordinates */ + antennas: Antenna[] = [] + + /** List of unique Antinode (x,y) coordinates */ + antinodes = new Set() + + /** Array indices used in traversing the board and antennas objects */ + currentAntIndex: number = 0 + nextAntIndex: number = 1 + + /** + * Creates an instance of the `GridAntiNodes` class + * @constructor + * @param {string[][]} inputFile 2D string array containing grid paths and `Antennas` + */ + constructor(inputFile: string[][]) { + this.board = inputFile + this.findAntennas() + } + + /** + * Finds and stores the (x,y) coordinates of valid `Antennas` + * from the 2D string array into the `this.antennas[]` array + */ + findAntennas (): void { + for (let row = 0; row < this.board.length; row += 1) { + const indices: Antenna[] = this.board[row]!.reduce((list: Antenna[], item, index) => { + if (item.match(/^[A-Za-z0-9]+$/g)) { + this.board[row]![index] = item + + return [ + ...list, { frequency: item, x: index, y: row } + ] + } + + return list + }, []) + + this.antennas = [...this.antennas, ...indices] + } + } + + /** + * Increments the index counters used in traversing the `Antinodes` list + */ + incrementCursors (): void { + if (this.nextAntIndex === this.antennas.length - 1) { + this.currentAntIndex += 1 + this.nextAntIndex = this.currentAntIndex + 1 + } else { + this.nextAntIndex += 1 + } + } + + /** + * Resets the index counters used in traversing the `Antinodes` list + */ + resetCursors (): void { + this.currentAntIndex = 0 + this.nextAntIndex = 1 + } + + /** + * Stores the (x,y) coordinates of an `Antinode` + * @param {string} antinodeCoord String-concatenated (x,y) coordinate of an `Antinode` + */ + storeAntinode (antinodeCoord: string): void { + this.antinodes.add(antinodeCoord) + } + + /** + * Prints the 2D grid (board) to the screen + * @param {boolean} withAntinodes Flag to display the `Antinodes` in the grid + */ + printGrid (withAntinodes: boolean = false): void { + if (withAntinodes) { + for (const antinode of this.antinodes) { + const coord = (antinode as string).split(',').map(item => Number(item)) + const character = this.board[coord[0] as number]![coord[1] as number] + + if (character === '.') { + this.board[coord[0] as number]![coord[1] as number] = '#' + } + } + } + + console.log(this.board.map(row => row.join(' '))) + } +} diff --git a/src/2024/2024-12-08/lib/types.ts b/src/2024/2024-12-08/lib/types.ts new file mode 100644 index 0000000..868a9c2 --- /dev/null +++ b/src/2024/2024-12-08/lib/types.ts @@ -0,0 +1,8 @@ +export interface Point { + x: number; + y: number; +} + +export interface Antenna extends Point { + frequency: string; +} diff --git a/src/2024/2024-12-08/lib/uniqueAntinodes.ts b/src/2024/2024-12-08/lib/uniqueAntinodes.ts new file mode 100644 index 0000000..12c2882 --- /dev/null +++ b/src/2024/2024-12-08/lib/uniqueAntinodes.ts @@ -0,0 +1,60 @@ +import type { Antenna, Point } from './types.js' +import { GridAntiNodes } from './GridAntinodes.js' + +/** + * Counts the unique locations in the grid that contains an antinode + * @param {string[][]} inputFile 2D string array containing grid paths and `Antennas` + * @returns {number} Total number of unique antinodes in the grid + */ +export const countAntinodes = (inputFile: string[][]): number => { + const grid = new GridAntiNodes(inputFile) + + while(grid.currentAntIndex < grid.antennas.length - 1) { + // Antennas + const a1 = grid.antennas[grid.currentAntIndex] as Antenna + const a2 = grid.antennas[grid.nextAntIndex] as Antenna + + // Skip processing antennas with different frequencies + if (a1.frequency !== a2.frequency) { + grid.incrementCursors() + continue + } + + // Antenna coordinate difference + const diff = { + x: a2.x - a1.x, + y: a2.y - a1.y + } + + // Antinode 1 coordinates + const node1: Point = { + x: a1.x - diff.x, + y: a1.y - diff.y + } + + // Antinode 2 coordinates + const node2: Point = { + x: a2.x + diff.x, + y: a2.y + diff.y + } + + if ( + node1.y < inputFile.length && node1.y >= 0 && + node1.x < inputFile[0]!.length && node1.x >= 0 + ) { + grid.storeAntinode(`${node1.y},${node1.x}`) + } + + if ( + node2.y < inputFile.length && node2.y >= 0 && + node2.x < inputFile[0]!.length && node2.x >= 0 + ) { + grid.storeAntinode(`${node2.y},${node2.x}`) + } + + grid.incrementCursors() + } + + grid.printGrid(true) + return grid.antinodes.size +} diff --git a/src/2024/2024-12-08/main.ts b/src/2024/2024-12-08/main.ts new file mode 100644 index 0000000..b760aa3 --- /dev/null +++ b/src/2024/2024-12-08/main.ts @@ -0,0 +1,21 @@ +import path from 'path' +import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' +import { currentDirectory } from '@/utils/file.js' + +import { countAntinodes } from './lib/uniqueAntinodes.js' + +const input = readAOCInputFile({ + filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + type: AOC_OUTPUT_TYPE.STRING_ARRAY_2D +}) as string[][] + +/** + * Part 1/2 of the 2024-12-08 quiz + * Counts the unique locations in the grid that contains an antinode + */ +const quiz20241208_01 = () => { + const count = countAntinodes(input) + console.log('Antinodes in unique locations:', count) +} + +quiz20241208_01() diff --git a/src/2024/2024-12-08/sample.test.ts b/src/2024/2024-12-08/sample.test.ts new file mode 100644 index 0000000..b5f07ef --- /dev/null +++ b/src/2024/2024-12-08/sample.test.ts @@ -0,0 +1,16 @@ +import path from 'path' +import { test, expect } from 'vitest' + +import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' +import { currentDirectory } from '@/utils/file.js' + +import { countAntinodes } from './lib/uniqueAntinodes.js' + +const input = readAOCInputFile({ + filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + type: AOC_OUTPUT_TYPE.STRING_ARRAY_2D +}) as string[][] + +test('Antinodes in unique locations', () => { + expect(countAntinodes(input)).toBe(14) +}) From 0d8138b4821960213828e1fa763f3b81376582b9 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Wed, 25 Dec 2024 19:32:20 +0800 Subject: [PATCH 15/33] chore: independent board for printing antinodes --- src/2024/2024-12-08/lib/GridAntinodes.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/2024/2024-12-08/lib/GridAntinodes.ts b/src/2024/2024-12-08/lib/GridAntinodes.ts index 482d7be..6cdd290 100644 --- a/src/2024/2024-12-08/lib/GridAntinodes.ts +++ b/src/2024/2024-12-08/lib/GridAntinodes.ts @@ -84,16 +84,20 @@ export class GridAntiNodes { */ printGrid (withAntinodes: boolean = false): void { if (withAntinodes) { + const printBoard = structuredClone(this.board) + for (const antinode of this.antinodes) { const coord = (antinode as string).split(',').map(item => Number(item)) const character = this.board[coord[0] as number]![coord[1] as number] if (character === '.') { - this.board[coord[0] as number]![coord[1] as number] = '#' + printBoard[coord[0] as number]![coord[1] as number] = '#' } } - } - console.log(this.board.map(row => row.join(' '))) + console.log(printBoard.map(row => row.join(' '))) + } else { + console.log(this.board.map(row => row.join(' '))) + } } } From 28ebded5c7b65acca55cef4f3d535e3823c13865 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Thu, 26 Dec 2024 00:08:28 +0800 Subject: [PATCH 16/33] feat: day 8 resonant collinearity 2/2 soln, #13 --- src/2024/2024-12-08/README.md | 8 +- src/2024/2024-12-08/lib/GridAntinodes.ts | 10 ++- src/2024/2024-12-08/lib/allAntinodes.ts | 93 ++++++++++++++++++++++++ src/2024/2024-12-08/main.ts | 11 +++ src/2024/2024-12-08/sample.test.ts | 5 ++ 5 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 src/2024/2024-12-08/lib/allAntinodes.ts diff --git a/src/2024/2024-12-08/README.md b/src/2024/2024-12-08/README.md index f547952..b917a5e 100644 --- a/src/2024/2024-12-08/README.md +++ b/src/2024/2024-12-08/README.md @@ -3,7 +3,7 @@ Visit the Advent of Code website for more information on this puzzle at: **Source:** https://adventofcode.com/2024/day/8
-**Status:** On-going ⭐ +**Status:** Complete ⭐⭐ ## Code @@ -14,3 +14,9 @@ Visit the Advent of Code website for more information on this puzzle at: ### `uniqueAntinodes.ts` - `uniqueAntinodes()` - Counts the unique locations in the grid that contains an antinode + +### `allAntinodes.ts` + +- `getAntinodesInPath()` - Finds all `Antinode` coordinates along a path within a 2D array (grid) given a `Point`, increment steps and a +/- direction + +- `countAllAntinodes()` - Counts the unique locations in the grid that contains all locations of antinodes along a path diff --git a/src/2024/2024-12-08/lib/GridAntinodes.ts b/src/2024/2024-12-08/lib/GridAntinodes.ts index 6cdd290..9189711 100644 --- a/src/2024/2024-12-08/lib/GridAntinodes.ts +++ b/src/2024/2024-12-08/lib/GridAntinodes.ts @@ -8,14 +8,16 @@ export class GridAntiNodes { /** 2D string user input array */ board: string[][] = [] - /** Array containing Antenna (x,y) coordinates */ + /** Array containing Antenna (y,x) coordinates */ antennas: Antenna[] = [] - /** List of unique Antinode (x,y) coordinates */ - antinodes = new Set() + /** List of unique Antinode (y,x) coordinate strings */ + antinodes = new Set() - /** Array indices used in traversing the board and antennas objects */ + /** Grid array index pointing to the current antenna in the `this.antennas[]` list */ currentAntIndex: number = 0 + + /** Grid array index pointing to the next antenna after `this.currentAntIndex` */ nextAntIndex: number = 1 /** diff --git a/src/2024/2024-12-08/lib/allAntinodes.ts b/src/2024/2024-12-08/lib/allAntinodes.ts new file mode 100644 index 0000000..0e9efd6 --- /dev/null +++ b/src/2024/2024-12-08/lib/allAntinodes.ts @@ -0,0 +1,93 @@ +import type { Antenna, Point } from './types.js' +import { GridAntiNodes } from './GridAntinodes.js' + +/** + * Finds all `Antinode` coordinates along a path within a 2D array (grid) given a `Point`, increment steps and a +/- direction + * @param {Point} point (y,x) coordinate of an `Antenna` qualified for creating an `Antinode` + * @param {Point} increments Amount of increments to increase/decrease the (y,x) offsets of a `Point` + * @param {number} direction `+/-` positive or negative direction for increasing/decreasing a `Point`'s coordinates + * @typedef {Object} board Dimensions of the 2D grid array + * @param {number} board.length Length of the 2D array + * @param {number} board.width Width of the 2D array + * @returns {Set} All `Antinode` (y,x) coordinates along the path + */ +const getAntinodesInPath = ( + point: Point, + increments: Point, + direction: number, + board: { length: number, width: number } +): Set => { + const antinodes = new Set() + const startPoint = { ...point } + + while ( + startPoint.x >= 0 && startPoint.x < board.length && + startPoint.y >= 0 && startPoint.y < board.width + ) { + antinodes.add(`${startPoint.y},${startPoint.x}`) + + startPoint.x += increments.x * direction + startPoint.y += increments.y * direction + } + + return antinodes +} + +/** + * Counts the unique locations in the grid that contains all locations of antinodes along a path + * @param {string[][]} inputFile 2D string array containing grid paths and `Antennas` + * @returns {number} Total number of unique antinodes in the grid + */ +export const countAllAntinodes = (inputFile: string[][]): number => { + const grid = new GridAntiNodes(inputFile) + + while(grid.currentAntIndex < grid.antennas.length - 1) { + // Antennas + const a1 = grid.antennas[grid.currentAntIndex] as Antenna + const a2 = grid.antennas[grid.nextAntIndex] as Antenna + + const gridDimensions = { + length: grid.board.length, + width: grid.board[0]!.length + } + + // Skip processing antennas with different frequencies + if (a1.frequency !== a2.frequency) { + grid.incrementCursors() + continue + } + + // Antenna coordinate difference + const diff = { + x: a2.x - a1.x, + y: a2.y - a1.y + } + + if ( + a1.y < inputFile.length && a1.y >= 0 && + a1.x < inputFile[0]!.length && a1.x >= 0 + ) { + // Find all aligned antinodes + getAntinodesInPath(a1, diff, -1, gridDimensions) + .forEach( + item => grid.antinodes.add(item) + ) + } + + if ( + a2.y < inputFile.length && a2.y >= 0 && + a2.x < inputFile[0]!.length && a2.x >= 0 + ) { + // Find all aligned antinodes + getAntinodesInPath(a2, diff, 1, gridDimensions) + .forEach( + item => grid.antinodes.add(item) + ) + } + + grid.incrementCursors() + } + + grid.printGrid(true) + return grid.antinodes.size +} diff --git a/src/2024/2024-12-08/main.ts b/src/2024/2024-12-08/main.ts index b760aa3..b9643fd 100644 --- a/src/2024/2024-12-08/main.ts +++ b/src/2024/2024-12-08/main.ts @@ -3,6 +3,7 @@ import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' import { currentDirectory } from '@/utils/file.js' import { countAntinodes } from './lib/uniqueAntinodes.js' +import { countAllAntinodes } from './lib/allAntinodes.js' const input = readAOCInputFile({ filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), @@ -18,4 +19,14 @@ const quiz20241208_01 = () => { console.log('Antinodes in unique locations:', count) } +/** + * Part 2/2 of the 2024-12-08 quiz + * Counts the unique locations in the grid of all antinodes that contains an antinode + */ +const quiz20241208_02 = () => { + const count = countAllAntinodes(input) + console.log('All Antinodes count:', count) +} + quiz20241208_01() +quiz20241208_02() diff --git a/src/2024/2024-12-08/sample.test.ts b/src/2024/2024-12-08/sample.test.ts index b5f07ef..1ff3b3f 100644 --- a/src/2024/2024-12-08/sample.test.ts +++ b/src/2024/2024-12-08/sample.test.ts @@ -5,6 +5,7 @@ import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' import { currentDirectory } from '@/utils/file.js' import { countAntinodes } from './lib/uniqueAntinodes.js' +import { countAllAntinodes } from './lib/allAntinodes.js' const input = readAOCInputFile({ filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), @@ -14,3 +15,7 @@ const input = readAOCInputFile({ test('Antinodes in unique locations', () => { expect(countAntinodes(input)).toBe(14) }) + +test('All antinodes in line', () => { + expect(countAllAntinodes(input)).toBe(34) +}) From 26e31944098d9a1e1de3c02a4e002b4de9d0ef72 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Thu, 26 Dec 2024 00:09:21 +0800 Subject: [PATCH 17/33] chore: update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7f41a4c..e9e4d88 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ The codes are structured in a way that discusses and walks through the solution - Day 5: Print Queue [[link]](/src/2024/2024-12-05/README.md) - Day 6: Guard Gallivant [[link]](/src/2024/2024-12-06/README.md) - Day 7: Bridge Repair [[link]](/src/2024/2024-12-07/README.md) +- Day 8: Resonant Collinearity [[link]](/src/2024/2024-12-08/README.md) From 58b47773206af320db5064dd83fdf9f4bb30e53c Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Thu, 26 Dec 2024 00:19:01 +0800 Subject: [PATCH 18/33] chore: require node v20.15.0 or higher --- .npmrc | 1 + .nvmrc | 1 + README.md | 2 +- package-lock.json | 4 ++-- package.json | 4 ++-- 5 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 .npmrc create mode 100644 .nvmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..9075659 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20.15.0 diff --git a/README.md b/README.md index e9e4d88..18ce939 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Each Advent of Code (AOC) event quiz has its folder under **`"/src//=20.15.0", + "npm": ">=10.7.0" } }, "node_modules/@esbuild/aix-ppc64": { diff --git a/package.json b/package.json index 36368cb..270121f 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "dist/index.js", "type": "module", "engines": { - "node": "20.15.0", - "npm": "10.7.0" + "node": ">=20.15.0", + "npm": ">=10.7.0" }, "scripts": { "dev": "vitest", From 97c54290b203102a1bb74ee884e276248ab95060 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Thu, 26 Dec 2024 00:29:53 +0800 Subject: [PATCH 19/33] chore: use method --- src/2024/2024-12-08/lib/GridAntinodes.ts | 2 +- src/2024/2024-12-08/lib/allAntinodes.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/2024/2024-12-08/lib/GridAntinodes.ts b/src/2024/2024-12-08/lib/GridAntinodes.ts index 9189711..a335253 100644 --- a/src/2024/2024-12-08/lib/GridAntinodes.ts +++ b/src/2024/2024-12-08/lib/GridAntinodes.ts @@ -74,7 +74,7 @@ export class GridAntiNodes { /** * Stores the (x,y) coordinates of an `Antinode` - * @param {string} antinodeCoord String-concatenated (x,y) coordinate of an `Antinode` + * @param {string} antinodeCoord String-concatenated (y,x) coordinate of an `Antinode` */ storeAntinode (antinodeCoord: string): void { this.antinodes.add(antinodeCoord) diff --git a/src/2024/2024-12-08/lib/allAntinodes.ts b/src/2024/2024-12-08/lib/allAntinodes.ts index 0e9efd6..e63b7cb 100644 --- a/src/2024/2024-12-08/lib/allAntinodes.ts +++ b/src/2024/2024-12-08/lib/allAntinodes.ts @@ -70,7 +70,7 @@ export const countAllAntinodes = (inputFile: string[][]): number => { // Find all aligned antinodes getAntinodesInPath(a1, diff, -1, gridDimensions) .forEach( - item => grid.antinodes.add(item) + item => grid.storeAntinode(item) ) } @@ -81,7 +81,7 @@ export const countAllAntinodes = (inputFile: string[][]): number => { // Find all aligned antinodes getAntinodesInPath(a2, diff, 1, gridDimensions) .forEach( - item => grid.antinodes.add(item) + item => grid.storeAntinode(item) ) } From 1397c517ff25c046172f425e1fe5afdaac04dbd8 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Thu, 26 Dec 2024 05:16:02 +0800 Subject: [PATCH 20/33] feat: day 9 disk fragmenter 1/2 soln, #14 --- src/2024/2024-12-09/README.md | 19 ++++++++++ src/2024/2024-12-09/input.txt | 1 + src/2024/2024-12-09/lib/compact.ts | 51 ++++++++++++++++++++++++++ src/2024/2024-12-09/lib/disk.ts | 58 ++++++++++++++++++++++++++++++ src/2024/2024-12-09/main.ts | 24 +++++++++++++ src/2024/2024-12-09/sample.test.ts | 17 +++++++++ 6 files changed, 170 insertions(+) create mode 100644 src/2024/2024-12-09/README.md create mode 100644 src/2024/2024-12-09/input.txt create mode 100644 src/2024/2024-12-09/lib/compact.ts create mode 100644 src/2024/2024-12-09/lib/disk.ts create mode 100644 src/2024/2024-12-09/main.ts create mode 100644 src/2024/2024-12-09/sample.test.ts diff --git a/src/2024/2024-12-09/README.md b/src/2024/2024-12-09/README.md new file mode 100644 index 0000000..99a72e2 --- /dev/null +++ b/src/2024/2024-12-09/README.md @@ -0,0 +1,19 @@ +## Day 9: Disk Fragmenter + +Visit the Advent of Code website for more information on this puzzle at: + +**Source:** https://adventofcode.com/2024/day/8
+**Status:** On-going ⭐ + +## Code + +### `disk.ts` + +- **`Disk`** class - Object that provides common disk-like utility processing methods and stores processed data + - `createCharacterMap()` - Converts a disk map text into a character map + - `calculateDiskChecksum()` Calculates the check sum of a compacted disk files and spaces text map + +### `compact.ts` + +- **`CompactDisk`** class (extends the **`Disk`** class) - Object that compacts disk character symbols represeting file and space blocks from the right to the left-most free disk spaces + - `compactFileDisk()` - Moves file blocks one character at a time from the right to the left-most free disk spaces diff --git a/src/2024/2024-12-09/input.txt b/src/2024/2024-12-09/input.txt new file mode 100644 index 0000000..5ff5aae --- /dev/null +++ b/src/2024/2024-12-09/input.txt @@ -0,0 +1 @@ +2333133121414131402 \ No newline at end of file diff --git a/src/2024/2024-12-09/lib/compact.ts b/src/2024/2024-12-09/lib/compact.ts new file mode 100644 index 0000000..982f5d4 --- /dev/null +++ b/src/2024/2024-12-09/lib/compact.ts @@ -0,0 +1,51 @@ +import { Disk } from './disk.js' + +/** + * @class CompactDisk + * @extends Disk + * @inheritdoc + * @description Object that compacts disk character symbols represeting file and space blocks from the right to the left-most free disk spaces + */ +export class CompactDisk extends Disk { + /** + * Creates an instance of the `CompactDisk` class + * @constructor + * @param {string} diskMapText Series of numbers representing an alternating file and disk space blocks + */ + constructor(diskMapText: string) { + super(diskMapText) + this.compactFileDisk() + } + + /** + * Moves file blocks one character at a time from the right to the left-most free disk spaces of a disk character map. + * @param {string[]} [charMap] (Optional) Character mapping conversion of a disk map. Processes the local `this.map[]` if parameter is not provided. + * @returns {string} Compacted disk character array + */ + compactFileDisk (charMap?: string[]): string[] { + const map: string[] = [...(charMap ?? this.map)] + let charIndex = map.length - 1 + + // Total number of file (non-space) blocks + const filesCount = map.reduce( + (sum, item) => Number(item) >= 0 + ? sum += 1 + : sum, + 0 + ) + + while (charIndex >= filesCount) { + const dotIndex = map.indexOf('.') + + if (dotIndex !== -1) { + map[dotIndex] = map[charIndex] ?? '.' + map[charIndex] = '.' + } + + charIndex -= 1 + } + + this.compactMap = map + return map + } +} diff --git a/src/2024/2024-12-09/lib/disk.ts b/src/2024/2024-12-09/lib/disk.ts new file mode 100644 index 0000000..db316df --- /dev/null +++ b/src/2024/2024-12-09/lib/disk.ts @@ -0,0 +1,58 @@ +/** + * @class Disk + * @description Object that provides common disk-like utility processing methods and stores processed data + */ +export class Disk { + /** Character map conversion of a disk map string */ + map: string[] = [] + + /** Compacted disk character array */ + compactMap: string[] = [] + + /** + * Creates an instance of the `Disk` class + * @constructor + * @param {string} diskMapText Series of numbers representing an alternating file and disk space blocks + */ + constructor(diskMapText: string) { + this.createCharacterMap(diskMapText) + } + + /** + * Converts a disk map text into a character map, storing it in the `this.map[]` string array + * @param {string} diskMapText Series of numbers representing an alternating file and disk space blocks + * @returns {string[]} Character map conversion of a disk map string + */ + createCharacterMap (diskMapText: string): string[] { + const characters: string[] = [] + + for (let i = 0; i < diskMapText.length; i += 1) { + if ((i + 1) % 2 === 0) { + characters.push( + ...Array(Number(diskMapText[i])).fill('.') + ) + } else { + const itemID = i / 2 + characters.push( + ...Array(Number(diskMapText[i])).fill(itemID.toString()) + ) + } + } + + this.map = characters + return characters + } + + /** + * Calculates the check sum of a compacted disk files and spaces text map + * @param {string[]} [compactFileText] (Optional) Compacted disk map resulting from the `this.compactFileDisk()` function. Processes the local `this.compactMap[]` data if parameter is not provided. + * @returns {number} Check sum - sum of file block IDs multiplied with their positions + */ + calculateDiskChecksum (compactFileText?: string[]): number { + return (compactFileText || this.compactMap) + .reduce((sum, item, index) => { + if (item === '.') return sum + return sum + Number(item) * index + }, 0) + } +} diff --git a/src/2024/2024-12-09/main.ts b/src/2024/2024-12-09/main.ts new file mode 100644 index 0000000..d2c7869 --- /dev/null +++ b/src/2024/2024-12-09/main.ts @@ -0,0 +1,24 @@ +import path from 'path' + +import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' +import { currentDirectory } from '@/utils/file.js' + +import { CompactDisk } from './lib/compact.js' + +const input = readAOCInputFile({ + filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + type: AOC_OUTPUT_TYPE.STRING +}) as string + +/** + * Part 1/2 of the 2024-12-09 quiz + * Counts the check sum of a compacted disk represented by strings of text + */ +export const quiz20241209_01 = () => { + const disk = new CompactDisk(input) + const sum = disk.calculateDiskChecksum() + + console.log('Compacted disk checksum:', sum) +} + +quiz20241209_01() diff --git a/src/2024/2024-12-09/sample.test.ts b/src/2024/2024-12-09/sample.test.ts new file mode 100644 index 0000000..2bc7406 --- /dev/null +++ b/src/2024/2024-12-09/sample.test.ts @@ -0,0 +1,17 @@ +import path from 'path' +import { test, expect } from 'vitest' + +import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' +import { currentDirectory } from '@/utils/file.js' + +import { CompactDisk } from './lib/compact.js' + +const input = readAOCInputFile({ + filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + type: AOC_OUTPUT_TYPE.STRING +}) as string + +test('Compacted disk checksum', () => { + const disk = new CompactDisk(input) + expect(disk.calculateDiskChecksum()).toBe(1928) +}) From 18ae4b85500968317cef55e259c37020a6602212 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Thu, 26 Dec 2024 05:18:40 +0800 Subject: [PATCH 21/33] chore: use random non-aoc provided input --- src/2024/2024-12-09/input.txt | 2 +- src/2024/2024-12-09/sample.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/2024/2024-12-09/input.txt b/src/2024/2024-12-09/input.txt index 5ff5aae..ae6fb74 100644 --- a/src/2024/2024-12-09/input.txt +++ b/src/2024/2024-12-09/input.txt @@ -1 +1 @@ -2333133121414131402 \ No newline at end of file +233313312141413 \ No newline at end of file diff --git a/src/2024/2024-12-09/sample.test.ts b/src/2024/2024-12-09/sample.test.ts index 2bc7406..accb865 100644 --- a/src/2024/2024-12-09/sample.test.ts +++ b/src/2024/2024-12-09/sample.test.ts @@ -13,5 +13,5 @@ const input = readAOCInputFile({ test('Compacted disk checksum', () => { const disk = new CompactDisk(input) - expect(disk.calculateDiskChecksum()).toBe(1928) + expect(disk.calculateDiskChecksum()).toBe(967) }) From 2ac3e83814bf8c221dee7b44968c57469bf6a967 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Thu, 26 Dec 2024 05:43:26 +0800 Subject: [PATCH 22/33] chore: print logs --- src/2024/2024-12-09/lib/compact.ts | 26 +++++++++++++++++++++----- src/2024/2024-12-09/lib/disk.ts | 2 +- src/2024/2024-12-09/main.ts | 2 +- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/2024/2024-12-09/lib/compact.ts b/src/2024/2024-12-09/lib/compact.ts index 982f5d4..dbc3e50 100644 --- a/src/2024/2024-12-09/lib/compact.ts +++ b/src/2024/2024-12-09/lib/compact.ts @@ -1,10 +1,15 @@ import { Disk } from './disk.js' +interface CompactParams { + charMap?: string[]; + printLog? : boolean; +} + /** * @class CompactDisk * @extends Disk * @inheritdoc - * @description Object that compacts disk character symbols represeting file and space blocks from the right to the left-most free disk spaces + * @description Object that compacts disk character symbols representing file and space blocks from the right to the left-most free disk spaces */ export class CompactDisk extends Disk { /** @@ -12,19 +17,21 @@ export class CompactDisk extends Disk { * @constructor * @param {string} diskMapText Series of numbers representing an alternating file and disk space blocks */ - constructor(diskMapText: string) { + constructor(diskMapText: string, printLog: boolean = false) { super(diskMapText) - this.compactFileDisk() + this.compactFileDisk({ printLog }) } /** * Moves file blocks one character at a time from the right to the left-most free disk spaces of a disk character map. * @param {string[]} [charMap] (Optional) Character mapping conversion of a disk map. Processes the local `this.map[]` if parameter is not provided. + * @param {boolean} printLog Flag to display the step-by-step file movement on screen. Defaults to `false` * @returns {string} Compacted disk character array */ - compactFileDisk (charMap?: string[]): string[] { - const map: string[] = [...(charMap ?? this.map)] + compactFileDisk (params: CompactParams): string[] { + const map: string[] = [...(params?.charMap || this.map)] let charIndex = map.length - 1 + let logs = '' // Total number of file (non-space) blocks const filesCount = map.reduce( @@ -37,6 +44,10 @@ export class CompactDisk extends Disk { while (charIndex >= filesCount) { const dotIndex = map.indexOf('.') + if (params?.printLog) { + logs += map.join('') + '\n' + } + if (dotIndex !== -1) { map[dotIndex] = map[charIndex] ?? '.' map[charIndex] = '.' @@ -45,6 +56,11 @@ export class CompactDisk extends Disk { charIndex -= 1 } + if (params?.printLog) { + logs += map.join('') + console.log(logs) + } + this.compactMap = map return map } diff --git a/src/2024/2024-12-09/lib/disk.ts b/src/2024/2024-12-09/lib/disk.ts index db316df..c7a78d8 100644 --- a/src/2024/2024-12-09/lib/disk.ts +++ b/src/2024/2024-12-09/lib/disk.ts @@ -44,7 +44,7 @@ export class Disk { } /** - * Calculates the check sum of a compacted disk files and spaces text map + * Calculates and returns the check sum of a compacted disk files and spaces text map * @param {string[]} [compactFileText] (Optional) Compacted disk map resulting from the `this.compactFileDisk()` function. Processes the local `this.compactMap[]` data if parameter is not provided. * @returns {number} Check sum - sum of file block IDs multiplied with their positions */ diff --git a/src/2024/2024-12-09/main.ts b/src/2024/2024-12-09/main.ts index d2c7869..6360abd 100644 --- a/src/2024/2024-12-09/main.ts +++ b/src/2024/2024-12-09/main.ts @@ -15,7 +15,7 @@ const input = readAOCInputFile({ * Counts the check sum of a compacted disk represented by strings of text */ export const quiz20241209_01 = () => { - const disk = new CompactDisk(input) + const disk = new CompactDisk(input, true) const sum = disk.calculateDiskChecksum() console.log('Compacted disk checksum:', sum) From 1f816851d161011b53ea6a2898c59210b76b2076 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 27 Dec 2024 01:40:49 +0800 Subject: [PATCH 23/33] feat: day 9 disk fragmenter 2/2 soln, #14 --- README.md | 1 + src/2024/2024-12-09/README.md | 25 +++-- src/2024/2024-12-09/lib/compact.ts | 25 +++-- src/2024/2024-12-09/lib/disk.ts | 53 ++++++++--- src/2024/2024-12-09/lib/types.ts | 33 +++++++ src/2024/2024-12-09/lib/whole.ts | 143 +++++++++++++++++++++++++++++ src/2024/2024-12-09/main.ts | 9 ++ src/2024/2024-12-09/sample.test.ts | 8 +- 8 files changed, 264 insertions(+), 33 deletions(-) create mode 100644 src/2024/2024-12-09/lib/types.ts create mode 100644 src/2024/2024-12-09/lib/whole.ts diff --git a/README.md b/README.md index 18ce939..1b4f4da 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ The codes are structured in a way that discusses and walks through the solution - Day 6: Guard Gallivant [[link]](/src/2024/2024-12-06/README.md) - 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) diff --git a/src/2024/2024-12-09/README.md b/src/2024/2024-12-09/README.md index 99a72e2..921eaf9 100644 --- a/src/2024/2024-12-09/README.md +++ b/src/2024/2024-12-09/README.md @@ -3,17 +3,30 @@ Visit the Advent of Code website for more information on this puzzle at: **Source:** https://adventofcode.com/2024/day/8
-**Status:** On-going ⭐ +**Status:** Complete ⭐⭐ ## Code ### `disk.ts` -- **`Disk`** class - Object that provides common disk-like utility processing methods and stores processed data - - `createCharacterMap()` - Converts a disk map text into a character map - - `calculateDiskChecksum()` Calculates the check sum of a compacted disk files and spaces text map +**`Disk`** class - Object that provides common disk-like utility processing methods and stores processed data with the following methods: + +- **`createCharacterMap()`** - Converts a disk map text into a character map, storing it in the `this.map[]` string array +- **`calculateDiskChecksum()`** Calculates the check sum of a fragmented disk files and spaces text map +- **`getGrid()`** - Joins the `this.map[]` disk and spaces character array into a string format if its less than `this.maxArrayToPrintLength` +- **`getCompactGrid()`** - Joins the `this.compactMap[]` "compact" disk and spaces character array into a string format if its less than `this.maxArrayToPrintLength` + ### `compact.ts` -- **`CompactDisk`** class (extends the **`Disk`** class) - Object that compacts disk character symbols represeting file and space blocks from the right to the left-most free disk spaces - - `compactFileDisk()` - Moves file blocks one character at a time from the right to the left-most free disk spaces +**`CompactDisk`** class (extends the **`Disk`** class) - Object that compacts disk character symbols representing file and space blocks from the right to the left-most free disk spaces with the following methods: + +- **`defragmentation()`** - Moves file blocks one character at a time from the right to the left-most free disk spaces of a disk character map, Storing the result in the `this.compactMap[]` and returning the result. + +### `whole.ts` + +**WholeDisk** class - Object that compacts disk character symbols representing files and spaces by moving whole (groups) of file blocks into spaces that can accommodate them with the following methods: + +- **`findDiskBlocks()`** - Finds and intializes the array indices of file blocks and spaces, noting their lengths +- **`findFreeSpaceBlock()`** - Finds the index of a group of free space blocks that can contain the whole length of a `FileBlock` in the `this.map[]` array from the `this.spaceBlock` map +- **`defragmentation()`** - Moves whole file blocks from the right to the left-most free disk spaces that can contain the whole files blocks, Storing the result in the `this.compactMap[]` and returning the result. diff --git a/src/2024/2024-12-09/lib/compact.ts b/src/2024/2024-12-09/lib/compact.ts index dbc3e50..0550af2 100644 --- a/src/2024/2024-12-09/lib/compact.ts +++ b/src/2024/2024-12-09/lib/compact.ts @@ -1,9 +1,5 @@ import { Disk } from './disk.js' - -interface CompactParams { - charMap?: string[]; - printLog? : boolean; -} +import type { CompactParams } from './types.js' /** * @class CompactDisk @@ -16,20 +12,22 @@ export class CompactDisk extends Disk { * Creates an instance of the `CompactDisk` class * @constructor * @param {string} diskMapText Series of numbers representing an alternating file and disk space blocks + * @param {boolean} printLog Flag to display the step-by-step file movement on screen. Defaults to `false` */ constructor(diskMapText: string, printLog: boolean = false) { super(diskMapText) - this.compactFileDisk({ printLog }) + this.defragmentation({ printLog }) } /** - * Moves file blocks one character at a time from the right to the left-most free disk spaces of a disk character map. - * @param {string[]} [charMap] (Optional) Character mapping conversion of a disk map. Processes the local `this.map[]` if parameter is not provided. - * @param {boolean} printLog Flag to display the step-by-step file movement on screen. Defaults to `false` - * @returns {string} Compacted disk character array + * Moves file blocks one character at a time from the right to the left-most free disk spaces of a disk character map, + * Storing the result in the `this.compactMap[]` and returning the result. + * @param {CompactParams} params - Parameters for input map string array. + * @returns {string[]} Array of defragmented files and spaces blocks */ - compactFileDisk (params: CompactParams): string[] { + defragmentation (params: CompactParams): string[] { const map: string[] = [...(params?.charMap || this.map)] + let charIndex = map.length - 1 let logs = '' @@ -45,9 +43,10 @@ export class CompactDisk extends Disk { const dotIndex = map.indexOf('.') if (params?.printLog) { - logs += map.join('') + '\n' + logs += this.getGrid(map) } + // Swap file and space locations one unit at a time if (dotIndex !== -1) { map[dotIndex] = map[charIndex] ?? '.' map[charIndex] = '.' @@ -57,7 +56,7 @@ export class CompactDisk extends Disk { } if (params?.printLog) { - logs += map.join('') + logs += this.getGrid(map) console.log(logs) } diff --git a/src/2024/2024-12-09/lib/disk.ts b/src/2024/2024-12-09/lib/disk.ts index c7a78d8..9e4bbb1 100644 --- a/src/2024/2024-12-09/lib/disk.ts +++ b/src/2024/2024-12-09/lib/disk.ts @@ -3,11 +3,14 @@ * @description Object that provides common disk-like utility processing methods and stores processed data */ export class Disk { - /** Character map conversion of a disk map string */ - map: string[] = [] + /** 1-dimensional character array conversion of a disk map string */ + protected map: string[] = [] /** Compacted disk character array */ - compactMap: string[] = [] + protected compactMap: string[] = [] + + /** Maximum number of array elements to allow printing as string in console.log() */ + maxArrayToPrintLength: number = 10000 /** * Creates an instance of the `Disk` class @@ -21,31 +24,29 @@ export class Disk { /** * Converts a disk map text into a character map, storing it in the `this.map[]` string array * @param {string} diskMapText Series of numbers representing an alternating file and disk space blocks - * @returns {string[]} Character map conversion of a disk map string + * @returns {string[]} Character array conversion of a disk map string */ createCharacterMap (diskMapText: string): string[] { - const characters: string[] = [] - for (let i = 0; i < diskMapText.length; i += 1) { - if ((i + 1) % 2 === 0) { - characters.push( + if (i % 2 === 1) { + this.map.push( ...Array(Number(diskMapText[i])).fill('.') ) } else { const itemID = i / 2 - characters.push( + + this.map.push( ...Array(Number(diskMapText[i])).fill(itemID.toString()) ) } } - this.map = characters - return characters + return this.map } /** - * Calculates and returns the check sum of a compacted disk files and spaces text map - * @param {string[]} [compactFileText] (Optional) Compacted disk map resulting from the `this.compactFileDisk()` function. Processes the local `this.compactMap[]` data if parameter is not provided. + * Calculates the check sum of a fragmented disk files and spaces text map + * @param {string[]} [compactFileText] (Optional) Compacted disk map resulting from the `this.defragmentation()` function. Processes the local `this.compactMap[]` data if parameter is not provided. * @returns {number} Check sum - sum of file block IDs multiplied with their positions */ calculateDiskChecksum (compactFileText?: string[]): number { @@ -55,4 +56,30 @@ export class Disk { return sum + Number(item) * index }, 0) } + + /** + * Joins the `this.map[]` disk and spaces character array into a string format if its less than `this.maxArrayToPrintLength` + * @param {string} [map] (Optional) Character string array similar to `this.map[]`. Uses the `this.map[]` array by default. + * @returns {string} linear string/text version of the `this.map[]` character array + */ + getGrid (map?: string[]): string { + if (map?.length ?? this.map.length <= this.maxArrayToPrintLength) { + return (map ?? this.map).join('') + '\n' + } + + return '' + } + + /** + * Joins the `this.compactMap[]` "compact" disk and spaces character array into a string format if its less than `this.maxArrayToPrintLength` + * @param {string} [map] (Optional) Character string array similar to `this.map[]`. Uses the `this.compactMap[]` array by default. + * @returns {string} linear string/text version of the `this.compactMap[]` character array + */ + getCompactGrid (map?: string[]): string { + if ((map?.length ?? this.compactMap.length) <= this.maxArrayToPrintLength) { + return (map ?? this.compactMap).join('') + '\n' + } + + return '' + } } diff --git a/src/2024/2024-12-09/lib/types.ts b/src/2024/2024-12-09/lib/types.ts new file mode 100644 index 0000000..d393e11 --- /dev/null +++ b/src/2024/2024-12-09/lib/types.ts @@ -0,0 +1,33 @@ +/** + * Parameters for input map string array + * @interface CompactParams + * @param {string} [charMap] - (Optional) Character mapping conversion of a disk map. Processes the local `this.map[]` if parameter is not provided. + * @param {boolean} [printLog] - (Optional) Flag to display the step-by-step file block movement on screen. + */ +export interface CompactParams { + charMap?: string[]; + printLog? : boolean; +} + +/** + * Properties of a system file block + * @interface FileBlock + * @param {number} length - Length of a file block - number of units that it occupies in the `this.map[]` array + * @param {number} index - Starting array index of a file block in the `this.map[]` array + */ +export interface FileBlock { + length: number; + index: number; +} + +/** + * Properties of a space (free space) block + * @interface SpaceBlock + * @param {number} occupied - Number of space block units occupied by a `FileBlock`. A `SpaceBlock` is full if its `occupied` + * and `length` properties are equal + */ +export interface SpaceBlock extends FileBlock { + occupied: number; +} + + diff --git a/src/2024/2024-12-09/lib/whole.ts b/src/2024/2024-12-09/lib/whole.ts new file mode 100644 index 0000000..69e7bd9 --- /dev/null +++ b/src/2024/2024-12-09/lib/whole.ts @@ -0,0 +1,143 @@ +import { Disk } from './disk.js' +import type { CompactParams, FileBlock, SpaceBlock } from './types.js' + +/** + * @class WholeDisk + * @extends Disk + * @inheritdoc + * @description Object that compacts disk character symbols representing files and spaces by moving whole (groups) of file blocks into spaces that can accommodate them + */ +export class WholeDisk extends Disk { + /** Structure for tracking the indices, length and availability of `SpaceBlock` disk space blocks */ + spaceBlocks = new Map() + + /** Structure for tracking the indices and lengths of `FileBlock` file blocks */ + fileBlocks = new Map() + + /** + * Creates an instance of the `WholeDisk` class + * @constructor + * @param {string} diskMapText Series of numbers representing an alternating file and disk space files + */ + constructor(diskMapText: string, printLog: boolean = false) { + super(diskMapText) + this.defragmentation({ printLog }) + } + + /** + * Finds and intializes the array indices of file blocks and spaces, noting their lengths + * @returns {void} + */ + findDiskBlocks (): void { + let spaceCount = 0 + + this.map.forEach((digit, index) => { + if (digit !== '.') { + if (!this.fileBlocks.has(digit)) { + this.fileBlocks.set(digit, { length: 1, index }) + } else { + this.fileBlocks.get(digit)!.length += 1 + } + + if (spaceCount > 0) { + this.spaceBlocks.set(String(index - spaceCount), { + length: spaceCount, + index: index - spaceCount, + occupied: 0 + }) + + spaceCount = 0 + } + } else { + spaceCount += 1 + } + }) + } + + /** + * Finds the index of a group of free space blocks that can contain the whole length of a `FileBlock` in the `this.map[]` array from the `this.spaceBlock` map + * @param {number} fileBlockLength Length of a `FileBlock` - number of spaces it occupies in the `this.map[]` array + * @param {number} fileStartIndex Starting index of a `FileBlock` in the `this.map[]` array + * @returns {number} Start array index in the `this.map[]` of a valid free space that can contain the whole length of a file block + */ + findFreeSpaceBlock (fileBlockLength: number, fileStartIndex: number): number { + let indexOfSpace = -1 + + for (const item of this.spaceBlocks.values()) { + if ( + // Space units already occupied with file blocks plus expected file block length is less than or equal + // the spaces (length) it can accommodate + item.occupied + fileBlockLength <= item.length && + // The max units (spaces) should be large enough to contain the file length + item.length >= fileBlockLength && + // Space block's index (position in the this.map[]) array is lower than the file's starting index to avoid repitition + item.index < fileStartIndex + ) { + indexOfSpace = item.index + break + } + } + + return indexOfSpace + } + + /** + * Moves whole file blocks from the right to the left-most free disk spaces that can contain the whole files blocks, + * Storing the result in the `this.compactMap[]` and returning the result. + * @typedef {CompactParams} params - Parameters for input map string array. + * @returns {string[]} Array of defragmented files and spaces blocks + */ + defragmentation (params: CompactParams): string[] { + const map: string[] = structuredClone(params.charMap ?? this.map) + let logs = '' + + // Find the locations and stats of files and spaces + this.findDiskBlocks() + + // Ascending order file IDs + const digitKeys = Array.from(this.fileBlocks.keys()) + + for (let i = digitKeys.length - 1; i >= 0; i -= 1) { + const key = digitKeys[i] + + // Full file block + const file = this.fileBlocks.get(String(key)) + + if (file === undefined) continue + + // Find the available free disk space location that can fit the full file block + const fileBlockLength = file.length + const spaceIndex = this.findFreeSpaceBlock(fileBlockLength, file.index) + + const space = this.spaceBlocks.get(String(spaceIndex)) + if (space === undefined) continue + + const start = space.index + space.occupied + const end = start + fileBlockLength + + // Move files to the free space + for (let j = start; j < end; j += 1) { + map[j] = String(key) + space.occupied += 1 + } + + // Remove file trace from the old location + for ( + let k = file.index; + k < file.index + fileBlockLength; + k += 1 + ) { + map[k] = '.' + } + + logs += this.getCompactGrid(map) + } + + if (params?.printLog) { + console.log(logs) + } + + this.compactMap = map + return map + } +} diff --git a/src/2024/2024-12-09/main.ts b/src/2024/2024-12-09/main.ts index 6360abd..7a048d3 100644 --- a/src/2024/2024-12-09/main.ts +++ b/src/2024/2024-12-09/main.ts @@ -4,6 +4,7 @@ import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' import { currentDirectory } from '@/utils/file.js' import { CompactDisk } from './lib/compact.js' +import { WholeDisk } from './lib/whole.js' const input = readAOCInputFile({ filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), @@ -21,4 +22,12 @@ export const quiz20241209_01 = () => { console.log('Compacted disk checksum:', sum) } +export const quiz20241209_02 = () => { + const disk = new WholeDisk(input, true) + const sum = disk.calculateDiskChecksum() + + console.log('Disk - whole file blocks checksum:', sum) +} + quiz20241209_01() +quiz20241209_02() diff --git a/src/2024/2024-12-09/sample.test.ts b/src/2024/2024-12-09/sample.test.ts index accb865..ca1d259 100644 --- a/src/2024/2024-12-09/sample.test.ts +++ b/src/2024/2024-12-09/sample.test.ts @@ -5,13 +5,19 @@ import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' import { currentDirectory } from '@/utils/file.js' import { CompactDisk } from './lib/compact.js' +import { WholeDisk } from './lib/whole.js' const input = readAOCInputFile({ filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), type: AOC_OUTPUT_TYPE.STRING }) as string -test('Compacted disk checksum', () => { +test('Defragmented disk checksum', () => { const disk = new CompactDisk(input) expect(disk.calculateDiskChecksum()).toBe(967) }) + +test('Defragmented disk - (move whole file blocks) checksum', () => { + const disk = new WholeDisk(input) + expect(disk.calculateDiskChecksum()).toBe(1440) +}) From 93abfc0a059d5540157791824fdeca0b6c92c39e Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 27 Dec 2024 02:09:58 +0800 Subject: [PATCH 24/33] chore: update docs --- src/2024/2024-12-09/README.md | 18 +++++++++--------- src/2024/2024-12-09/lib/compact.ts | 4 ++-- src/2024/2024-12-09/lib/disk.ts | 10 +++++----- src/2024/2024-12-09/lib/whole.ts | 6 +++--- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/2024/2024-12-09/README.md b/src/2024/2024-12-09/README.md index 921eaf9..52082a0 100644 --- a/src/2024/2024-12-09/README.md +++ b/src/2024/2024-12-09/README.md @@ -11,22 +11,22 @@ Visit the Advent of Code website for more information on this puzzle at: **`Disk`** class - Object that provides common disk-like utility processing methods and stores processed data with the following methods: -- **`createCharacterMap()`** - Converts a disk map text into a character map, storing it in the `this.map[]` string array -- **`calculateDiskChecksum()`** Calculates the check sum of a fragmented disk files and spaces text map -- **`getGrid()`** - Joins the `this.map[]` disk and spaces character array into a string format if its less than `this.maxArrayToPrintLength` -- **`getCompactGrid()`** - Joins the `this.compactMap[]` "compact" disk and spaces character array into a string format if its less than `this.maxArrayToPrintLength` +- **`createCharacterMap()`** - Converts files and spaces disk text representation into a character map, storing it in the `this.map[]` string array +- **`calculateDiskChecksum()`** - Calculates the checksum of fragmented or defragmented disk files and spaces text map +- **`getGrid()`** - Joins the `this.map[]` disk and spaces character array into a string format if it is less than `this.maxArrayToPrintLength` +- **`getCompactGrid()`** - Joins the `this.compactMap[]` "compact" disk and spaces character array into a string format if it is less than `this.maxArrayToPrintLength` ### `compact.ts` **`CompactDisk`** class (extends the **`Disk`** class) - Object that compacts disk character symbols representing file and space blocks from the right to the left-most free disk spaces with the following methods: -- **`defragmentation()`** - Moves file blocks one character at a time from the right to the left-most free disk spaces of a disk character map, Storing the result in the `this.compactMap[]` and returning the result. +- **`defragmentation()`** - Moves file blocks one character at a time from the right to the left-most free disk spaces of a disk character map, Storing the result in the `this.compactMap[]` and returning the result. ### `whole.ts` -**WholeDisk** class - Object that compacts disk character symbols representing files and spaces by moving whole (groups) of file blocks into spaces that can accommodate them with the following methods: +**WholeDisk** class - Object that compacts disk character symbols representing files and spaces by moving whole (groups) of file blocks into spaces that can accommodate them with the following methods: -- **`findDiskBlocks()`** - Finds and intializes the array indices of file blocks and spaces, noting their lengths -- **`findFreeSpaceBlock()`** - Finds the index of a group of free space blocks that can contain the whole length of a `FileBlock` in the `this.map[]` array from the `this.spaceBlock` map -- **`defragmentation()`** - Moves whole file blocks from the right to the left-most free disk spaces that can contain the whole files blocks, Storing the result in the `this.compactMap[]` and returning the result. +- **`findDiskBlocks()`** - Finds and initializes the array indices of file blocks and spaces, noting their lengths for tracking +- **`findFreeSpaceBlock()`** - Finds the starting index of a group of free space blocks that can contain the whole length of a `FileBlock` in the `this.map[]` array from the `this.spaceBlocks` block map +- **`defragmentation()`** - Moves whole file blocks from the right to the left-most free disk spaces that can contain the whole file blocks, Storing the result in the this.compactMap[] and returning the result. diff --git a/src/2024/2024-12-09/lib/compact.ts b/src/2024/2024-12-09/lib/compact.ts index 0550af2..3e1f6ce 100644 --- a/src/2024/2024-12-09/lib/compact.ts +++ b/src/2024/2024-12-09/lib/compact.ts @@ -20,8 +20,8 @@ export class CompactDisk extends Disk { } /** - * Moves file blocks one character at a time from the right to the left-most free disk spaces of a disk character map, - * Storing the result in the `this.compactMap[]` and returning the result. + * Moves file blocks one character at a time from the right to the left-most free disk spaces of + * a disk character map, storing the result in the `this.compactMap[]` and returning the result. * @param {CompactParams} params - Parameters for input map string array. * @returns {string[]} Array of defragmented files and spaces blocks */ diff --git a/src/2024/2024-12-09/lib/disk.ts b/src/2024/2024-12-09/lib/disk.ts index 9e4bbb1..a257a55 100644 --- a/src/2024/2024-12-09/lib/disk.ts +++ b/src/2024/2024-12-09/lib/disk.ts @@ -1,6 +1,6 @@ /** * @class Disk - * @description Object that provides common disk-like utility processing methods and stores processed data + * @description Object that provides common disk-like utility processing methods and stores processed data. */ export class Disk { /** 1-dimensional character array conversion of a disk map string */ @@ -22,7 +22,7 @@ export class Disk { } /** - * Converts a disk map text into a character map, storing it in the `this.map[]` string array + * Converts files and spaces disk text representation into a character map, storing it in the `this.map[]` string array * @param {string} diskMapText Series of numbers representing an alternating file and disk space blocks * @returns {string[]} Character array conversion of a disk map string */ @@ -45,7 +45,7 @@ export class Disk { } /** - * Calculates the check sum of a fragmented disk files and spaces text map + * Calculates the checksum of fragmented or defragmented disk files and spaces text map * @param {string[]} [compactFileText] (Optional) Compacted disk map resulting from the `this.defragmentation()` function. Processes the local `this.compactMap[]` data if parameter is not provided. * @returns {number} Check sum - sum of file block IDs multiplied with their positions */ @@ -58,7 +58,7 @@ export class Disk { } /** - * Joins the `this.map[]` disk and spaces character array into a string format if its less than `this.maxArrayToPrintLength` + * Joins the `this.map[]` disk and spaces character array into a string format if it is less than `this.maxArrayToPrintLength` * @param {string} [map] (Optional) Character string array similar to `this.map[]`. Uses the `this.map[]` array by default. * @returns {string} linear string/text version of the `this.map[]` character array */ @@ -71,7 +71,7 @@ export class Disk { } /** - * Joins the `this.compactMap[]` "compact" disk and spaces character array into a string format if its less than `this.maxArrayToPrintLength` + * Joins the `this.compactMap[]` "compact" disk and spaces character array into a string format if it is less than `this.maxArrayToPrintLength` * @param {string} [map] (Optional) Character string array similar to `this.map[]`. Uses the `this.compactMap[]` array by default. * @returns {string} linear string/text version of the `this.compactMap[]` character array */ diff --git a/src/2024/2024-12-09/lib/whole.ts b/src/2024/2024-12-09/lib/whole.ts index 69e7bd9..95bc617 100644 --- a/src/2024/2024-12-09/lib/whole.ts +++ b/src/2024/2024-12-09/lib/whole.ts @@ -5,7 +5,7 @@ import type { CompactParams, FileBlock, SpaceBlock } from './types.js' * @class WholeDisk * @extends Disk * @inheritdoc - * @description Object that compacts disk character symbols representing files and spaces by moving whole (groups) of file blocks into spaces that can accommodate them + * @description Object that compacts disk character symbols representing files and spaces by moving whole (groups) of file blocks into spaces that can accommodate them. */ export class WholeDisk extends Disk { /** Structure for tracking the indices, length and availability of `SpaceBlock` disk space blocks */ @@ -25,7 +25,7 @@ export class WholeDisk extends Disk { } /** - * Finds and intializes the array indices of file blocks and spaces, noting their lengths + * Finds and initializes the array indices of file blocks and spaces, noting their lengths for tracking. * @returns {void} */ findDiskBlocks (): void { @@ -55,7 +55,7 @@ export class WholeDisk extends Disk { } /** - * Finds the index of a group of free space blocks that can contain the whole length of a `FileBlock` in the `this.map[]` array from the `this.spaceBlock` map + * Moves whole file blocks from the right to the left-most free disk spaces that can contain the whole file blocks, Storing the result in the `this.compactMap[]` and returning the result. * @param {number} fileBlockLength Length of a `FileBlock` - number of spaces it occupies in the `this.map[]` array * @param {number} fileStartIndex Starting index of a `FileBlock` in the `this.map[]` array * @returns {number} Start array index in the `this.map[]` of a valid free space that can contain the whole length of a file block From ecfe773cbcf5101bde77d1af16f4c31ce5443774 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 27 Dec 2024 02:13:03 +0800 Subject: [PATCH 25/33] fix: print logs --- src/2024/2024-12-09/input.txt | 2 +- src/2024/2024-12-09/lib/disk.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/2024/2024-12-09/input.txt b/src/2024/2024-12-09/input.txt index ae6fb74..83722da 100644 --- a/src/2024/2024-12-09/input.txt +++ b/src/2024/2024-12-09/input.txt @@ -1 +1 @@ -233313312141413 \ No newline at end of file +2841627148755595347924125852582654605174565397553360307432589096303483571124734584586210354123601330562823412141224024641070135033171488643018485383593864593613944290247592914710571182835389418251596615864128489342335128113774221660213974216969398155455112866521431943259293819242686263845488731685111619229729517931885686874442886455721061212991835568306211134681185190383257271117107918166185933079346964864898663615467531495079149063124634998634345647489411407328598862985320436896267494488838418070513724661271424172204156792316894989168380835935996262545396512884409535431824504121143434862992281365504281523615342988701914411617238513395510289969759770732710332816893238125637802991404645271621563473138686868240673337448678613876266290319988464080276680328055548344824897917086424986689459238298618096717045554218298599258743701765545313786129578155627671869329264847775649541323598127741575384571823336938166589296593337501444601240743664496081536374414879571291211029838158182677236755899041153683222744799487466713592266635314206553729738316947403691798023415427348431415891332538143325801272494165537296385712205071262681693065713143205766925596767581152564462226489098437910234474628688262731866667154496638830669512853216657353589436599236948153293811867272412859637679991222744248927445785926869449635294609564386520313682568664833625301394346126526787469656496542421146604293985245113055829255189140658323144422274914247057761846768682821159841072295813284887935817441382359419697418924323319947726113367334259080955524908515875157906482457061644150235557844747956876886528449273965533505312332657582727521069887294372730299273637444927872395738161298442792866920675616457378445643235530516638109811993852864376259674535623929518715710159084125350161272993436885091653344604987727538496120296641493357547434679215475535774479985238172557131999922511565427915028453179298233814079123236364768382746786577267088513562703710936681959197955579234026964170387282257035588615902241511456661271138340365830323712277466195454262556914572215772811956652936557129131772401257857816629745537472983791247250351983393063964810294313424755698873768932503488304468184121487239841340391973348037982328486236356518468268157489934654653290751517901461402937873820144221346274468352588142494356454039553683828539838428623163779216952744311240132448274973641888161774838093207791877384119528236041972395771611278375353482384565744018126983255585528618126329645861198054461572226893211341919528163716318036201329469092617282267329952218239967126876868977542056617689488231175267465616974893674248397248518882576261406095167467296620813424835754886012596243875385618946211925505168531577775164602365806271456025526130687934346928653663994210172017495881969388163144159297219747954667856013616158498399635596647827613683662429173353208071627949935839936219239616192372261388225860536329429180567566603537527461613029116964232947291013678078465377599132246022578464408064174220774980387450825552306481785178655149662972371744252262318871392876949186412862716919399644731479784016845247579825605122999187173527935729497060121586904568274616529325926851731672195599827092701353535433327057745889558539769457227459236473538962909289635027136923415167425318195920679445969043901662431673204552632784139694333149982385319280302451203491909239313110748148189044395992655346683265218614838345245978853750985037878030335362103294806348564647296113335233324045899692346940916429391957546869823113264943391812248419771771766285809778182390559536561377509136991588727530546578966964744245733464151851582177148882157221708827743774864264252768649752141718452092795213791767394750873813583210571340804481872345233151215885876831798491775536722218642724201330276983536146216747241196928738332010909481896330467848444192592663498769658229504528451750938814281271136055948684353270458690378619505669382983206275869951621590506167869512563274489319637663389253159420937149524097997767729242814542687797478885315413531566423364996520185193728846862050712167319028184275585480731025928548946323788195827783855920576368912839479059838439232067426190998778604734438774861898712214152035288338284447291444112894123392214316634179638130657091611226941322259543261331333986471759688175269813717719285638756039507852759611977312375047181890751172759363338817974923647665568458577676472686906550867611724746919060882515795377732536429996194460738484404869224275323563398065886451574725569875743923282771745782674193948972492793447529316087221128693036158851625773637511913511566450748068803563269069581926642526998737921630893010581032902795211042712850909899524893383220633267342039397897124921944944954146216144678755118276261189753173542263782142806076244164122581534573549739253811369226834428875148879538507195208191357829182925881516652863229570434971798689595960289092588792983331684480347135848793683676842065318032144457662077483974516741621442252568554236844924651718142844148681132499732941677828325389787911502855785746173559996115692461328238224531531988774958145132647140332830513823593444259930473247554685622266545966453073971786129415856867647835486064423198428163303333994619595294748620822177652863542895958014516348913671636922886824112928363254852634865186868970857519169339868873964080166517578557822397142716614254667359109727503914785637494328573750724141438091918897628651837394932246248035702448682047505531165293977443451929375039383595906641805872869114346175511397129828464998855913902370686495363548888260566091211717521029641258548416668890497869855327665010653310341733944357528490539027979845256092575731736131103174722262529276127616108021833216616855654442911650519112448095937933653212976194345928579692689165474450875557574996675081763173477170355850663750524864363183883651768435382945866664755342669213979421969020978667787428392055757187126476188239683665535020491576746356481568558273529452359470219459229686256221417361892589405129503519674260359694453260452946387977844844548618677218758288668577348599942186276495486336439794206575232256966722557945107156116348705316294916244971598468163173348673552483298882604489105371996155808925602423577223114824781080967178782251707614264894244650962512784782584334404513623129359248327357628437742622536542644833763178656190834833401137736225651499745090908439833699175641776554876917845747323857485889261248596883484447185873393190704997792925177078166768888525311893219725159522162913734853698178374592723460197577239622866955261622258763796879983264319769348361801182534474708244198166971769462524614925809518373464417677944292544490487573557260216214181612213092592958391246535481341817508112462072122769652585301954542691676776259267514838274086909390625081524843367470974299676721637413646342227637941621414630143568817815438644328938316866549481239213779491159320549421122275117890513343961043971497791855138017154946636911478872978716956066191622995634124374121188836548227386905610875476687658453485233628501552957040925823878335164190944251331736685865911079777090657554572089223042107952628177822127564069694793292288118338446358781037994361932774986554231095147069577685644049831887922575211282582884408336753692643920407718885683975567368843812380798984458437643657646566419188817526846492125995715475122518587079709815971737408452325832788768464014684164126152106097865793745018307218728933543973901974342092361896153152769477491225248053608991661040132146228521792898172361902412537914245597537948984582528717426288466111259918707249933576107812507916136271257277999346984916788227711092192045478227465025983398748316841015914634422397814693352638678881613959607683379389814882775338196397826677121189749798419697378527603535589569253855238263862217108161375232561219921543706126774948592937349316208769644854214912782195344484381781643594251626669372189851174182163186589971972662827426792345316113396739986490663744738226519332738070538099508130364333952493742891628561497756515758286730341732729156821447135331642557114197856337189046602525843817619916473369473448632664938314415160945790176743191418681634687119874989753259521661944843629988175488633739407179891154757170285128836491237159892721718328552817371852156427822015248484681080725256753619574488392598997963737265312639235878943376525930237567615050707257849420356948532782267089368267513651133191929413532590778419764561218111399041488565565128327315223762334947416362665448756076327377273995197999467197972456885327743259291277215861773662388822993476609957416616564569301715843015442032945089899443785088105677481216455288732414838787236195896935221698813486967893869282927396692653113464997152772440725493783625622089675645911939567179467929791057145112754632937768949759262776888087954566202433608956991721279753349266803288501145867940445690891986715871299239455380899516591727533641225447222985981457896131273067173674994031487911722192996770206178528551412237274249273034973340334574926378871970515847868775661260876092822243988714665950975282401912113694577637251019769444665112818892549975887853377470509444334591765157171049745786572216111716107248699452849785662992371932688256566064196884246063747529971726255729596492594486824551839167568817936878623449456025977465919221781710935695859932854223471864174236891047429432254792889312895811209184489684419837141855222424322338642567382890927459135041444511719727892738256767426298592261402988624238253280243615115664121158364898976767833492735129358361298110596697494225398351969478608584313946756957965379686565495952682540854977115263157019742696888880729040487342429945373982444370886116266378583640704079765767646642373830561010788244192523254435251133748122825714967473393189595831583249618798501118826127442517801611495982624410929323608718309335276476118111325115853282724871161357232545474166113648986081229677104563717649174631747036441646137912616992199468433151174735824089796394958476781431857661817317569978736511145232938397181072181192205156527427597031313345648961246336815637763528894921281388553043827086877256143416329397399879478466111270355321409647919684309630997011642745502018953417476067195536387162442569132987785870961544943047321969625612403937871586103163505875575387209569715228703419508577489259815576855534636020703090125945174713624685349033569776553430342770926662547280565517737144413545328046926152102375781520664817679264276528942714864637185944923123205961226835213685138932142350245737333480834344148626289782289283554837299842561713745576349665122061211589405267245189475767129436988267352617502283172884185433839791533738536174383931808490834357111542196482102575254111894021979857604454854339125434285746787751825770918476252290325354665482581714875087408821168864173066878315994294346948995641856011846422695684649073288252881635934184673667578897559646926336257335993727113434573347937856713338494680318575376569149236445256363293408356872839649153257551711064897030446811951639864565448765494991167041478577342179284038512538523631783944823792113623494285471352385170866079404958232278458497659946897541385075923170929958488124491112397052152486789612507211203114679159198289432636272783947075365663899539947947431890287360204949364771885582953727636440916747682723785734767642824330619658314928675470869666889177619796816780618655405519424353516093376389129427589590877729768794469093168487532393279116332964376553545387704639625256171171604654253089707722588852289371135659609886906398387010119763343373283261842248117966466222942193355492381829843146351589112521193279406716894790107570865786572913919671823126153247693790255788635020437929176474932328539168844160639783218313171545231832674229553534565721136933662431741453947885689590606846695397853154348765119163904165982721343410525665311753669319633477953766207267108842512937337387175367122428605647447670487779146728518068334276978296944636809749721265421342634420726647658110244832195633552683296526415429323455795799904327284021647950616560836992868071114754963070929535691448231438424391651619708598985369612043211421965020836465377196962241893522718035306517793391556172783993746161464450137868608045226449543613898334334525323717907158229337502031156932394251707228649562391540615786969999445964108912663728375557345724142363285944525927317431578428618788613588724880745580256766106939712572833354878320447568298373267087457943672816839312318292941556465616493851473358662751637234772368761858877577574151206171715651203734465950855754148353704163874627305321879276579929263994359080486540568131346374995257331357734313389778549234827332623765466171785375131059224446281658931042985282872966487053693597494088387977275554249584181043389513663694897448939699846490327195378374341769963392372569144422901878985243971149134696165025444542723950826227681273314012591966641366997246748240365824336140399448354540922294468793716310233133102289987376411128885343222325628599212889631830776665888128528460317355688560157060756777843687738282132945902970821076948269738262223642708493319221619766257741579852723714557851786012451722303394422549256778633786347124427137425397195578579183891411195863611917281968297998209159706797712991258094352049968186208329358366579660513136521472851962622321486795872181443375128340629731432197365128347822762913961890304381993860226031848151617749143675989684577222808840973232722341218020947769136789147096817240598322451811964219134296734182242357521797193192688270834051761655532768692832537995267375919076973451489431871085348550587181707260116426162296879482244920824358593039372744413860653773665779582028165164705464689623642380644712364311297850194838447830156097869513646295849826299154225189641864606920999533641978711744533993562257533262987550305378807920987777909391907497725769582491137780335097991615189733375177897386547683825635186497131414528770289418888032356683116726404420895772739624802499238372925662797876289258526962833135598262147626864523166938275868678838794189499426498649266515173777932550843924968027329957843851788312429832202519682979364658585016728176996980776851868559781888621926499485428848879727931091626333322184791093903380891557463738911960821793976195622097343568786142845919963912365111207442527177602420731847615997689624101263864054181765277875592792991534165885494152673338561288513888114559195476462463669163351154227494955120116864916755406345584727616542716456388149282767697551622993598530216135338871547597897348857725109136302486443954797656788973141386143090381057933541439067499387543511112119782553294550522717733070793889244813318093993342303993286321565487638634923069385846485231691820252190954247158271314719388951876473898829884920918662299070471592162915769280843268186061617666414667724020257480869385225797551928996226951428343366943744993069745210839561493218451796931739754160504114465238723986711329331274683717383596283243772693976217429346603130224699279138696930652493534974992095881112895955837233604030847991259599348871566156106752151543699469657981655139601310448468541515502713372093995032956597578489509254251948357667948037326474465150631565144244691378343465773356911141448494142040735567131880803413507958756468475686519613391616522937634539518291582610927923408288375223547122341645646940987225773579792077115360685253144997265493192250935482549452211247429651362246949076148194114581801755358276669868271214612127882728449149409081895711132245576677539595932026306085209847478639869376905876919762827239122118322085757426795434641531535216505423978076468119246788377137106023447252377157217351679036139327191380478681984086778548354428225036155530145978168575614946743976847863716171469954386295456057705314266292381059434820612122216116566337483672441457523186933544341548812332717680454749748280902723763488788880359871795758628465173915733994545751877349992426865956514272369617239416794559235547661538522846263554141983217332254585392684432137692271123641239955529860368385807856751946718393636330444840375464701825181615302384484410351254988226504622737272894614138098302146736028272275356061976742679618666929902740872729524129611243886218421461333118696786305486449578232638141374932390649537993083799966878955503071294384457160575171698874646688955893421644926890777998897764398930629041737923566983817736258236785963641369407418362626229051265458252728505850776861146712394649509168132695936916964123704697113989869961979531728572194230226416993654249567769156167640269224175490129666877753588816706768974288352136559132301768657770873199841786605314415878285736576154672365672511535184466914467056207768896725412837892812569895699442815056702943408631157812216125794035355374304861359913135440845527258688381158447880131649823726885046691574373885819084595439921333971375507671951950801910435158302053201958677990222554392799912985883296705528821795807514664319341459789142129581683355558798588964255671661448371532626244939358466315117164978556405945812065112960205845712316849025936351135552616875214664801612217775987750434224657516137490788719908258458077416039461280579064312033692598101391392294133281971424528179642546116675822594655773663558648499316092948949388091579250318517829287602147252670897388991218697921458620571313912086641564397294293154145899121636251034865591454614597813849893648493692758754911747730194474668044538773827789166991196961105718516714961341173019162391664338263515141553548579295765961975457215383986276490215760815835301440685962277327876231676442159310414647862135774846215077264327475844568664973498354045218327995096619728618789929616488228741112506366579084889117531360591371753458191349703836408466311352639397912946114249704276246224533450234560488739812781714882287287899820597214473780502094795091585588535391923417492142948284911083698898397366758020887028829813235667407881752774139565591142797236505272891754374735735888208036916487301483468126957739385428966688844564282989252174941693209675114273377997174854542775768448362453872475415353694236927490237686148785865479867795615833345192573164207988966587938681309120254029644189397217603862245557335828906668387263356017845747819216292141189821477181165612421047404631571173669825967891953915108275714182405622919918922614709246742618587624199837761171641516873193331155243466123484159145785213775232323195444237421333926983963854962039207465185746841663399475275312861445438361322585728789523036118061967861739754709199908072552098938411135927302592299470632348641642317990252494486041265439972068621815155014909818734749711244633397552855981968661867963170577396224881458818485535459087805627756994612145841038944839758823648678718354192745828142278852541947142455323126698520403074746360847849342193403248719492503116479760973497739581591198853897356773266726797825991016339616946448679844954841986932535862582110706922599686191316481633172719152874411112861925582439944037902182404493723984916420164975793712744963472532462443419038652863677639287198308363916135357022359972568481563626529382392180548238724829236624287888414222852885645951422914271685357157216989458160478324383845389595203953644845361983552484908670268066831047412757715965505527381191181610807168886969827815786242409983902925775653759376649499444187846767678182249288544066436088236978233676127935552540937256711559511450341773424415636820953278788957208434416254758768233669537216835938591771127027124233832176153469547814456463169115336339691199956533875793895260502573354271138859379526568996546136702394118644638262586962797765312745306065384165803625774763366143666419393720611737353824162939282411686066788936547286889953328513988832761124292890136323324193138173494977953834559987189918646125731065431119242990489964383999528063603152671776661980592098243646797681377261112238359425912573693764137268408542361466135311634770653578458189932112156613903517254672796628367917554840729843278029667852498188567351973982872647928676143544472246477610211546965024237611418262743722713958436799675881196553524248602891753534773781807041416063792746857645875791516344909663814160401864522813722662129376305 \ No newline at end of file diff --git a/src/2024/2024-12-09/lib/disk.ts b/src/2024/2024-12-09/lib/disk.ts index a257a55..a7336bf 100644 --- a/src/2024/2024-12-09/lib/disk.ts +++ b/src/2024/2024-12-09/lib/disk.ts @@ -63,7 +63,7 @@ export class Disk { * @returns {string} linear string/text version of the `this.map[]` character array */ getGrid (map?: string[]): string { - if (map?.length ?? this.map.length <= this.maxArrayToPrintLength) { + if ((map?.length ?? this.map.length) <= this.maxArrayToPrintLength) { return (map ?? this.map).join('') + '\n' } From 25a1a9d679db4238b35abbdf2ef2a908718d788e Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 27 Dec 2024 02:16:44 +0800 Subject: [PATCH 26/33] chore: use a random non-aoc input --- src/2024/2024-12-09/input.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/2024/2024-12-09/input.txt b/src/2024/2024-12-09/input.txt index 83722da..ae6fb74 100644 --- a/src/2024/2024-12-09/input.txt +++ b/src/2024/2024-12-09/input.txt @@ -1 +1 @@ -2841627148755595347924125852582654605174565397553360307432589096303483571124734584586210354123601330562823412141224024641070135033171488643018485383593864593613944290247592914710571182835389418251596615864128489342335128113774221660213974216969398155455112866521431943259293819242686263845488731685111619229729517931885686874442886455721061212991835568306211134681185190383257271117107918166185933079346964864898663615467531495079149063124634998634345647489411407328598862985320436896267494488838418070513724661271424172204156792316894989168380835935996262545396512884409535431824504121143434862992281365504281523615342988701914411617238513395510289969759770732710332816893238125637802991404645271621563473138686868240673337448678613876266290319988464080276680328055548344824897917086424986689459238298618096717045554218298599258743701765545313786129578155627671869329264847775649541323598127741575384571823336938166589296593337501444601240743664496081536374414879571291211029838158182677236755899041153683222744799487466713592266635314206553729738316947403691798023415427348431415891332538143325801272494165537296385712205071262681693065713143205766925596767581152564462226489098437910234474628688262731866667154496638830669512853216657353589436599236948153293811867272412859637679991222744248927445785926869449635294609564386520313682568664833625301394346126526787469656496542421146604293985245113055829255189140658323144422274914247057761846768682821159841072295813284887935817441382359419697418924323319947726113367334259080955524908515875157906482457061644150235557844747956876886528449273965533505312332657582727521069887294372730299273637444927872395738161298442792866920675616457378445643235530516638109811993852864376259674535623929518715710159084125350161272993436885091653344604987727538496120296641493357547434679215475535774479985238172557131999922511565427915028453179298233814079123236364768382746786577267088513562703710936681959197955579234026964170387282257035588615902241511456661271138340365830323712277466195454262556914572215772811956652936557129131772401257857816629745537472983791247250351983393063964810294313424755698873768932503488304468184121487239841340391973348037982328486236356518468268157489934654653290751517901461402937873820144221346274468352588142494356454039553683828539838428623163779216952744311240132448274973641888161774838093207791877384119528236041972395771611278375353482384565744018126983255585528618126329645861198054461572226893211341919528163716318036201329469092617282267329952218239967126876868977542056617689488231175267465616974893674248397248518882576261406095167467296620813424835754886012596243875385618946211925505168531577775164602365806271456025526130687934346928653663994210172017495881969388163144159297219747954667856013616158498399635596647827613683662429173353208071627949935839936219239616192372261388225860536329429180567566603537527461613029116964232947291013678078465377599132246022578464408064174220774980387450825552306481785178655149662972371744252262318871392876949186412862716919399644731479784016845247579825605122999187173527935729497060121586904568274616529325926851731672195599827092701353535433327057745889558539769457227459236473538962909289635027136923415167425318195920679445969043901662431673204552632784139694333149982385319280302451203491909239313110748148189044395992655346683265218614838345245978853750985037878030335362103294806348564647296113335233324045899692346940916429391957546869823113264943391812248419771771766285809778182390559536561377509136991588727530546578966964744245733464151851582177148882157221708827743774864264252768649752141718452092795213791767394750873813583210571340804481872345233151215885876831798491775536722218642724201330276983536146216747241196928738332010909481896330467848444192592663498769658229504528451750938814281271136055948684353270458690378619505669382983206275869951621590506167869512563274489319637663389253159420937149524097997767729242814542687797478885315413531566423364996520185193728846862050712167319028184275585480731025928548946323788195827783855920576368912839479059838439232067426190998778604734438774861898712214152035288338284447291444112894123392214316634179638130657091611226941322259543261331333986471759688175269813717719285638756039507852759611977312375047181890751172759363338817974923647665568458577676472686906550867611724746919060882515795377732536429996194460738484404869224275323563398065886451574725569875743923282771745782674193948972492793447529316087221128693036158851625773637511913511566450748068803563269069581926642526998737921630893010581032902795211042712850909899524893383220633267342039397897124921944944954146216144678755118276261189753173542263782142806076244164122581534573549739253811369226834428875148879538507195208191357829182925881516652863229570434971798689595960289092588792983331684480347135848793683676842065318032144457662077483974516741621442252568554236844924651718142844148681132499732941677828325389787911502855785746173559996115692461328238224531531988774958145132647140332830513823593444259930473247554685622266545966453073971786129415856867647835486064423198428163303333994619595294748620822177652863542895958014516348913671636922886824112928363254852634865186868970857519169339868873964080166517578557822397142716614254667359109727503914785637494328573750724141438091918897628651837394932246248035702448682047505531165293977443451929375039383595906641805872869114346175511397129828464998855913902370686495363548888260566091211717521029641258548416668890497869855327665010653310341733944357528490539027979845256092575731736131103174722262529276127616108021833216616855654442911650519112448095937933653212976194345928579692689165474450875557574996675081763173477170355850663750524864363183883651768435382945866664755342669213979421969020978667787428392055757187126476188239683665535020491576746356481568558273529452359470219459229686256221417361892589405129503519674260359694453260452946387977844844548618677218758288668577348599942186276495486336439794206575232256966722557945107156116348705316294916244971598468163173348673552483298882604489105371996155808925602423577223114824781080967178782251707614264894244650962512784782584334404513623129359248327357628437742622536542644833763178656190834833401137736225651499745090908439833699175641776554876917845747323857485889261248596883484447185873393190704997792925177078166768888525311893219725159522162913734853698178374592723460197577239622866955261622258763796879983264319769348361801182534474708244198166971769462524614925809518373464417677944292544490487573557260216214181612213092592958391246535481341817508112462072122769652585301954542691676776259267514838274086909390625081524843367470974299676721637413646342227637941621414630143568817815438644328938316866549481239213779491159320549421122275117890513343961043971497791855138017154946636911478872978716956066191622995634124374121188836548227386905610875476687658453485233628501552957040925823878335164190944251331736685865911079777090657554572089223042107952628177822127564069694793292288118338446358781037994361932774986554231095147069577685644049831887922575211282582884408336753692643920407718885683975567368843812380798984458437643657646566419188817526846492125995715475122518587079709815971737408452325832788768464014684164126152106097865793745018307218728933543973901974342092361896153152769477491225248053608991661040132146228521792898172361902412537914245597537948984582528717426288466111259918707249933576107812507916136271257277999346984916788227711092192045478227465025983398748316841015914634422397814693352638678881613959607683379389814882775338196397826677121189749798419697378527603535589569253855238263862217108161375232561219921543706126774948592937349316208769644854214912782195344484381781643594251626669372189851174182163186589971972662827426792345316113396739986490663744738226519332738070538099508130364333952493742891628561497756515758286730341732729156821447135331642557114197856337189046602525843817619916473369473448632664938314415160945790176743191418681634687119874989753259521661944843629988175488633739407179891154757170285128836491237159892721718328552817371852156427822015248484681080725256753619574488392598997963737265312639235878943376525930237567615050707257849420356948532782267089368267513651133191929413532590778419764561218111399041488565565128327315223762334947416362665448756076327377273995197999467197972456885327743259291277215861773662388822993476609957416616564569301715843015442032945089899443785088105677481216455288732414838787236195896935221698813486967893869282927396692653113464997152772440725493783625622089675645911939567179467929791057145112754632937768949759262776888087954566202433608956991721279753349266803288501145867940445690891986715871299239455380899516591727533641225447222985981457896131273067173674994031487911722192996770206178528551412237274249273034973340334574926378871970515847868775661260876092822243988714665950975282401912113694577637251019769444665112818892549975887853377470509444334591765157171049745786572216111716107248699452849785662992371932688256566064196884246063747529971726255729596492594486824551839167568817936878623449456025977465919221781710935695859932854223471864174236891047429432254792889312895811209184489684419837141855222424322338642567382890927459135041444511719727892738256767426298592261402988624238253280243615115664121158364898976767833492735129358361298110596697494225398351969478608584313946756957965379686565495952682540854977115263157019742696888880729040487342429945373982444370886116266378583640704079765767646642373830561010788244192523254435251133748122825714967473393189595831583249618798501118826127442517801611495982624410929323608718309335276476118111325115853282724871161357232545474166113648986081229677104563717649174631747036441646137912616992199468433151174735824089796394958476781431857661817317569978736511145232938397181072181192205156527427597031313345648961246336815637763528894921281388553043827086877256143416329397399879478466111270355321409647919684309630997011642745502018953417476067195536387162442569132987785870961544943047321969625612403937871586103163505875575387209569715228703419508577489259815576855534636020703090125945174713624685349033569776553430342770926662547280565517737144413545328046926152102375781520664817679264276528942714864637185944923123205961226835213685138932142350245737333480834344148626289782289283554837299842561713745576349665122061211589405267245189475767129436988267352617502283172884185433839791533738536174383931808490834357111542196482102575254111894021979857604454854339125434285746787751825770918476252290325354665482581714875087408821168864173066878315994294346948995641856011846422695684649073288252881635934184673667578897559646926336257335993727113434573347937856713338494680318575376569149236445256363293408356872839649153257551711064897030446811951639864565448765494991167041478577342179284038512538523631783944823792113623494285471352385170866079404958232278458497659946897541385075923170929958488124491112397052152486789612507211203114679159198289432636272783947075365663899539947947431890287360204949364771885582953727636440916747682723785734767642824330619658314928675470869666889177619796816780618655405519424353516093376389129427589590877729768794469093168487532393279116332964376553545387704639625256171171604654253089707722588852289371135659609886906398387010119763343373283261842248117966466222942193355492381829843146351589112521193279406716894790107570865786572913919671823126153247693790255788635020437929176474932328539168844160639783218313171545231832674229553534565721136933662431741453947885689590606846695397853154348765119163904165982721343410525665311753669319633477953766207267108842512937337387175367122428605647447670487779146728518068334276978296944636809749721265421342634420726647658110244832195633552683296526415429323455795799904327284021647950616560836992868071114754963070929535691448231438424391651619708598985369612043211421965020836465377196962241893522718035306517793391556172783993746161464450137868608045226449543613898334334525323717907158229337502031156932394251707228649562391540615786969999445964108912663728375557345724142363285944525927317431578428618788613588724880745580256766106939712572833354878320447568298373267087457943672816839312318292941556465616493851473358662751637234772368761858877577574151206171715651203734465950855754148353704163874627305321879276579929263994359080486540568131346374995257331357734313389778549234827332623765466171785375131059224446281658931042985282872966487053693597494088387977275554249584181043389513663694897448939699846490327195378374341769963392372569144422901878985243971149134696165025444542723950826227681273314012591966641366997246748240365824336140399448354540922294468793716310233133102289987376411128885343222325628599212889631830776665888128528460317355688560157060756777843687738282132945902970821076948269738262223642708493319221619766257741579852723714557851786012451722303394422549256778633786347124427137425397195578579183891411195863611917281968297998209159706797712991258094352049968186208329358366579660513136521472851962622321486795872181443375128340629731432197365128347822762913961890304381993860226031848151617749143675989684577222808840973232722341218020947769136789147096817240598322451811964219134296734182242357521797193192688270834051761655532768692832537995267375919076973451489431871085348550587181707260116426162296879482244920824358593039372744413860653773665779582028165164705464689623642380644712364311297850194838447830156097869513646295849826299154225189641864606920999533641978711744533993562257533262987550305378807920987777909391907497725769582491137780335097991615189733375177897386547683825635186497131414528770289418888032356683116726404420895772739624802499238372925662797876289258526962833135598262147626864523166938275868678838794189499426498649266515173777932550843924968027329957843851788312429832202519682979364658585016728176996980776851868559781888621926499485428848879727931091626333322184791093903380891557463738911960821793976195622097343568786142845919963912365111207442527177602420731847615997689624101263864054181765277875592792991534165885494152673338561288513888114559195476462463669163351154227494955120116864916755406345584727616542716456388149282767697551622993598530216135338871547597897348857725109136302486443954797656788973141386143090381057933541439067499387543511112119782553294550522717733070793889244813318093993342303993286321565487638634923069385846485231691820252190954247158271314719388951876473898829884920918662299070471592162915769280843268186061617666414667724020257480869385225797551928996226951428343366943744993069745210839561493218451796931739754160504114465238723986711329331274683717383596283243772693976217429346603130224699279138696930652493534974992095881112895955837233604030847991259599348871566156106752151543699469657981655139601310448468541515502713372093995032956597578489509254251948357667948037326474465150631565144244691378343465773356911141448494142040735567131880803413507958756468475686519613391616522937634539518291582610927923408288375223547122341645646940987225773579792077115360685253144997265493192250935482549452211247429651362246949076148194114581801755358276669868271214612127882728449149409081895711132245576677539595932026306085209847478639869376905876919762827239122118322085757426795434641531535216505423978076468119246788377137106023447252377157217351679036139327191380478681984086778548354428225036155530145978168575614946743976847863716171469954386295456057705314266292381059434820612122216116566337483672441457523186933544341548812332717680454749748280902723763488788880359871795758628465173915733994545751877349992426865956514272369617239416794559235547661538522846263554141983217332254585392684432137692271123641239955529860368385807856751946718393636330444840375464701825181615302384484410351254988226504622737272894614138098302146736028272275356061976742679618666929902740872729524129611243886218421461333118696786305486449578232638141374932390649537993083799966878955503071294384457160575171698874646688955893421644926890777998897764398930629041737923566983817736258236785963641369407418362626229051265458252728505850776861146712394649509168132695936916964123704697113989869961979531728572194230226416993654249567769156167640269224175490129666877753588816706768974288352136559132301768657770873199841786605314415878285736576154672365672511535184466914467056207768896725412837892812569895699442815056702943408631157812216125794035355374304861359913135440845527258688381158447880131649823726885046691574373885819084595439921333971375507671951950801910435158302053201958677990222554392799912985883296705528821795807514664319341459789142129581683355558798588964255671661448371532626244939358466315117164978556405945812065112960205845712316849025936351135552616875214664801612217775987750434224657516137490788719908258458077416039461280579064312033692598101391392294133281971424528179642546116675822594655773663558648499316092948949388091579250318517829287602147252670897388991218697921458620571313912086641564397294293154145899121636251034865591454614597813849893648493692758754911747730194474668044538773827789166991196961105718516714961341173019162391664338263515141553548579295765961975457215383986276490215760815835301440685962277327876231676442159310414647862135774846215077264327475844568664973498354045218327995096619728618789929616488228741112506366579084889117531360591371753458191349703836408466311352639397912946114249704276246224533450234560488739812781714882287287899820597214473780502094795091585588535391923417492142948284911083698898397366758020887028829813235667407881752774139565591142797236505272891754374735735888208036916487301483468126957739385428966688844564282989252174941693209675114273377997174854542775768448362453872475415353694236927490237686148785865479867795615833345192573164207988966587938681309120254029644189397217603862245557335828906668387263356017845747819216292141189821477181165612421047404631571173669825967891953915108275714182405622919918922614709246742618587624199837761171641516873193331155243466123484159145785213775232323195444237421333926983963854962039207465185746841663399475275312861445438361322585728789523036118061967861739754709199908072552098938411135927302592299470632348641642317990252494486041265439972068621815155014909818734749711244633397552855981968661867963170577396224881458818485535459087805627756994612145841038944839758823648678718354192745828142278852541947142455323126698520403074746360847849342193403248719492503116479760973497739581591198853897356773266726797825991016339616946448679844954841986932535862582110706922599686191316481633172719152874411112861925582439944037902182404493723984916420164975793712744963472532462443419038652863677639287198308363916135357022359972568481563626529382392180548238724829236624287888414222852885645951422914271685357157216989458160478324383845389595203953644845361983552484908670268066831047412757715965505527381191181610807168886969827815786242409983902925775653759376649499444187846767678182249288544066436088236978233676127935552540937256711559511450341773424415636820953278788957208434416254758768233669537216835938591771127027124233832176153469547814456463169115336339691199956533875793895260502573354271138859379526568996546136702394118644638262586962797765312745306065384165803625774763366143666419393720611737353824162939282411686066788936547286889953328513988832761124292890136323324193138173494977953834559987189918646125731065431119242990489964383999528063603152671776661980592098243646797681377261112238359425912573693764137268408542361466135311634770653578458189932112156613903517254672796628367917554840729843278029667852498188567351973982872647928676143544472246477610211546965024237611418262743722713958436799675881196553524248602891753534773781807041416063792746857645875791516344909663814160401864522813722662129376305 \ No newline at end of file +233313312141413 \ No newline at end of file From 08e9965f28ffa16112547d8f86381d25dcac5b47 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Fri, 27 Dec 2024 02:46:18 +0800 Subject: [PATCH 27/33] chore: update docs --- src/2024/2024-12-09/README.md | 2 +- src/2024/2024-12-09/lib/whole.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/2024/2024-12-09/README.md b/src/2024/2024-12-09/README.md index 52082a0..70cc9e1 100644 --- a/src/2024/2024-12-09/README.md +++ b/src/2024/2024-12-09/README.md @@ -29,4 +29,4 @@ Visit the Advent of Code website for more information on this puzzle at: - **`findDiskBlocks()`** - Finds and initializes the array indices of file blocks and spaces, noting their lengths for tracking - **`findFreeSpaceBlock()`** - Finds the starting index of a group of free space blocks that can contain the whole length of a `FileBlock` in the `this.map[]` array from the `this.spaceBlocks` block map -- **`defragmentation()`** - Moves whole file blocks from the right to the left-most free disk spaces that can contain the whole file blocks, Storing the result in the this.compactMap[] and returning the result. +- **`defragmentation()`** - Moves whole file blocks from the right to the left-most free disk spaces that can contain the whole file blocks, Storing the result in the `this.compactMap[]` and returning the result. diff --git a/src/2024/2024-12-09/lib/whole.ts b/src/2024/2024-12-09/lib/whole.ts index 95bc617..ecaa955 100644 --- a/src/2024/2024-12-09/lib/whole.ts +++ b/src/2024/2024-12-09/lib/whole.ts @@ -55,7 +55,7 @@ export class WholeDisk extends Disk { } /** - * Moves whole file blocks from the right to the left-most free disk spaces that can contain the whole file blocks, Storing the result in the `this.compactMap[]` and returning the result. + * Finds the starting index of a group of free space blocks that can contain the whole length of a `FileBlock` in the `this.map[]` array from the `this.spaceBlocks` block map. * @param {number} fileBlockLength Length of a `FileBlock` - number of spaces it occupies in the `this.map[]` array * @param {number} fileStartIndex Starting index of a `FileBlock` in the `this.map[]` array * @returns {number} Start array index in the `this.map[]` of a valid free space that can contain the whole length of a file block From 59fbd7f0016e70dcb0cff5557ad1d882ff237c78 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sat, 28 Dec 2024 04:25:54 +0800 Subject: [PATCH 28/33] chore: refactor currentDirectory name to directory --- src/2024/2024-12-05/lib/fileReader.ts | 6 ++--- .../2024-12-06/lib/guardControllerLoop.ts | 6 ++--- src/2024/2024-12-06/main.ts | 4 ++-- src/2024/2024-12-06/sample.test.ts | 4 ++-- src/2024/2024-12-07/main.ts | 4 ++-- src/2024/2024-12-07/sample.test.ts | 4 ++-- src/2024/2024-12-08/main.ts | 4 ++-- src/2024/2024-12-08/sample.test.ts | 4 ++-- src/2024/2024-12-09/main.ts | 4 ++-- src/2024/2024-12-09/sample.test.ts | 4 ++-- src/utils/file.ts | 22 ++++++++++++++----- 11 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/2024/2024-12-05/lib/fileReader.ts b/src/2024/2024-12-05/lib/fileReader.ts index 3ab063f..48b12c9 100644 --- a/src/2024/2024-12-05/lib/fileReader.ts +++ b/src/2024/2024-12-05/lib/fileReader.ts @@ -1,5 +1,5 @@ import path from 'path' -import { currentDirectory, readFile } from '@/utils/file.js' +import { directory, readFile } from '@/utils/file.js' import { uniformArrayElements } from '@/utils/arrays.js' export type Rules = Record @@ -15,8 +15,8 @@ export type QuizData = { * @returns {QuizData} Formatted data */ export const fileReader = (fileName: string): QuizData => { - const directory = currentDirectory(import.meta.url) - const file = readFile(path.join(directory, '..', fileName)) + const fir = directory(import.meta.url) + const file = readFile(path.join(fir, '..', fileName)) const segments = file.split('\n\n') diff --git a/src/2024/2024-12-06/lib/guardControllerLoop.ts b/src/2024/2024-12-06/lib/guardControllerLoop.ts index 8463ce5..33d7307 100644 --- a/src/2024/2024-12-06/lib/guardControllerLoop.ts +++ b/src/2024/2024-12-06/lib/guardControllerLoop.ts @@ -106,14 +106,12 @@ export const findObstructionPositions = (data: string[][], printGrid: boolean = } const xData: string[][] = structuredClone(data) - const r = xData[y] as string[] - r[x] = '#' + xData[y]![x] = '#' const isLoop = gridHasInfiniteLoop(xData) if (isLoop) { - const e = extras[y] as string[] - e[x] = 'O' + extras[y]![x] = '0' loopCount += 1 } } diff --git a/src/2024/2024-12-06/main.ts b/src/2024/2024-12-06/main.ts index fdd1040..285dded 100644 --- a/src/2024/2024-12-06/main.ts +++ b/src/2024/2024-12-06/main.ts @@ -1,12 +1,12 @@ import path from 'path' import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' -import { currentDirectory } from '@/utils/file.js' +import { directory } from '@/utils/file.js' import { guardController } from './lib/guardController.js' import { findObstructionPositions } from './lib/guardControllerLoop.js' const file = readAOCInputFile({ - filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + filePath: path.join(directory(import.meta.url), 'input.txt'), type: AOC_OUTPUT_TYPE.STRING_ARRAY_2D }) as string [][] diff --git a/src/2024/2024-12-06/sample.test.ts b/src/2024/2024-12-06/sample.test.ts index aff13c1..d5629f5 100644 --- a/src/2024/2024-12-06/sample.test.ts +++ b/src/2024/2024-12-06/sample.test.ts @@ -2,13 +2,13 @@ import path from 'path' import { test, expect } from 'vitest' import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' -import { currentDirectory } from '@/utils/file.js' +import { directory } from '@/utils/file.js' import { guardController } from './lib/guardController.js' import { findObstructionPositions } from './lib/guardControllerLoop.js' const file = readAOCInputFile({ - filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + filePath: path.join(directory(import.meta.url), 'input.txt'), type: AOC_OUTPUT_TYPE.STRING_ARRAY_2D }) as string [][] diff --git a/src/2024/2024-12-07/main.ts b/src/2024/2024-12-07/main.ts index 56e619e..2db9ac2 100644 --- a/src/2024/2024-12-07/main.ts +++ b/src/2024/2024-12-07/main.ts @@ -1,13 +1,13 @@ import path from 'path' import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' -import { currentDirectory } from '@/utils/file.js' +import { directory } from '@/utils/file.js' import { totalCalibrationResult } from './lib/totalCalibration.js' import { totalCalibrationConcat } from './lib/totalCalibrationConcat.js' // Read and process the input file const input = (readAOCInputFile({ - filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + filePath: path.join(directory(import.meta.url), 'input.txt'), type: AOC_OUTPUT_TYPE.STRING }) as string) .split('\n') diff --git a/src/2024/2024-12-07/sample.test.ts b/src/2024/2024-12-07/sample.test.ts index 4872808..710bdba 100644 --- a/src/2024/2024-12-07/sample.test.ts +++ b/src/2024/2024-12-07/sample.test.ts @@ -2,14 +2,14 @@ import path from 'path' import { test, expect } from 'vitest' import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' -import { currentDirectory } from '@/utils/file.js' +import { directory } from '@/utils/file.js' import { totalCalibrationResult } from './lib/totalCalibration.js' import { totalCalibrationConcat } from './lib/totalCalibrationConcat.js' // Read and process the input file const input = (readAOCInputFile({ - filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + filePath: path.join(directory(import.meta.url), 'input.txt'), type: AOC_OUTPUT_TYPE.STRING }) as string) .split('\n') diff --git a/src/2024/2024-12-08/main.ts b/src/2024/2024-12-08/main.ts index b9643fd..68ace42 100644 --- a/src/2024/2024-12-08/main.ts +++ b/src/2024/2024-12-08/main.ts @@ -1,12 +1,12 @@ import path from 'path' import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' -import { currentDirectory } from '@/utils/file.js' +import { directory } from '@/utils/file.js' import { countAntinodes } from './lib/uniqueAntinodes.js' import { countAllAntinodes } from './lib/allAntinodes.js' const input = readAOCInputFile({ - filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + filePath: path.join(directory(import.meta.url), 'input.txt'), type: AOC_OUTPUT_TYPE.STRING_ARRAY_2D }) as string[][] diff --git a/src/2024/2024-12-08/sample.test.ts b/src/2024/2024-12-08/sample.test.ts index 1ff3b3f..43dd301 100644 --- a/src/2024/2024-12-08/sample.test.ts +++ b/src/2024/2024-12-08/sample.test.ts @@ -2,13 +2,13 @@ import path from 'path' import { test, expect } from 'vitest' import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' -import { currentDirectory } from '@/utils/file.js' +import { directory } from '@/utils/file.js' import { countAntinodes } from './lib/uniqueAntinodes.js' import { countAllAntinodes } from './lib/allAntinodes.js' const input = readAOCInputFile({ - filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + filePath: path.join(directory(import.meta.url), 'input.txt'), type: AOC_OUTPUT_TYPE.STRING_ARRAY_2D }) as string[][] diff --git a/src/2024/2024-12-09/main.ts b/src/2024/2024-12-09/main.ts index 7a048d3..8154672 100644 --- a/src/2024/2024-12-09/main.ts +++ b/src/2024/2024-12-09/main.ts @@ -1,13 +1,13 @@ import path from 'path' import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' -import { currentDirectory } from '@/utils/file.js' +import { directory } from '@/utils/file.js' import { CompactDisk } from './lib/compact.js' import { WholeDisk } from './lib/whole.js' const input = readAOCInputFile({ - filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + filePath: path.join(directory(import.meta.url), 'input.txt'), type: AOC_OUTPUT_TYPE.STRING }) as string diff --git a/src/2024/2024-12-09/sample.test.ts b/src/2024/2024-12-09/sample.test.ts index ca1d259..35ffb07 100644 --- a/src/2024/2024-12-09/sample.test.ts +++ b/src/2024/2024-12-09/sample.test.ts @@ -2,13 +2,13 @@ import path from 'path' import { test, expect } from 'vitest' import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' -import { currentDirectory } from '@/utils/file.js' +import { directory } from '@/utils/file.js' import { CompactDisk } from './lib/compact.js' import { WholeDisk } from './lib/whole.js' const input = readAOCInputFile({ - filePath: path.join(currentDirectory(import.meta.url), 'input.txt'), + filePath: path.join(directory(import.meta.url), 'input.txt'), type: AOC_OUTPUT_TYPE.STRING }) as string diff --git a/src/utils/file.ts b/src/utils/file.ts index 761ad13..7b65032 100644 --- a/src/utils/file.ts +++ b/src/utils/file.ts @@ -1,23 +1,33 @@ import fs from 'fs' -import { dirname } from 'path' +import path, { dirname } from 'path' import { fileURLToPath } from 'url' /** - * Get the full file path `"__dirname"` of the current directory of a module file + * 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"`) - * @param moduleFile {string} File URL of the current module being executed: `"import.meta.url"` - * @returns Full file path to the directory of the calling file/module also know as `__dirname` in CommonJS + * @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 */ -export const currentDirectory = (moduleFile: string): string => { +export const directory = (moduleFile: string): string => { const filePath = fileURLToPath(moduleFile) return dirname(filePath) } /** * Reads file from disk - * @param pathToFile Full file path to a target file + * @param {string} pathToFile - Full file path to a target file * @returns {string} String version of the file contents */ export const readFile = (pathToFile: string): string => { return fs.readFileSync(pathToFile, 'utf-8') } + +/** + * Get 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 + */ +export const file = (moduleFile: string, fileName: string) => { + return path.join(directory(moduleFile), fileName) +} From 8e559d0dadca623f9c49468c2afcc25afd915383 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sat, 28 Dec 2024 04:29:52 +0800 Subject: [PATCH 29/33] fix: unrenamed currentDirectory --- src/2024/2024-12-01/lib/fileReader.ts | 6 +++--- src/2024/2024-12-02/lib/fileReader.ts | 6 +++--- src/2024/2024-12-03/main.ts | 6 +++--- src/2024/2024-12-04/main.ts | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/2024/2024-12-01/lib/fileReader.ts b/src/2024/2024-12-01/lib/fileReader.ts index f3aa32a..704e7b0 100644 --- a/src/2024/2024-12-01/lib/fileReader.ts +++ b/src/2024/2024-12-01/lib/fileReader.ts @@ -1,5 +1,5 @@ import path from 'path' -import { readFile, currentDirectory } from '@/utils/file.js' +import { readFile, directory } from '@/utils/file.js' export type arrayLists = { list1: string[]; @@ -12,8 +12,8 @@ export type arrayLists = { */ export const fileReader = (): arrayLists => { // Read quiz input file - const directory = currentDirectory(import.meta.url) - const file = readFile(path.join(directory, '..', 'input.txt')) + const dir = directory(import.meta.url) + const file = readFile(path.join(dir, '..', 'input.txt')) const pairs: string[] = file.split('\n') const list1: string[] = [] diff --git a/src/2024/2024-12-02/lib/fileReader.ts b/src/2024/2024-12-02/lib/fileReader.ts index 24c6b4e..ab2ae39 100644 --- a/src/2024/2024-12-02/lib/fileReader.ts +++ b/src/2024/2024-12-02/lib/fileReader.ts @@ -1,5 +1,5 @@ import path from 'path' -import { readFile, currentDirectory } from '@/utils/file.js' +import { readFile, directory } from '@/utils/file.js' /** * Reads the quiz's input file into two (2) string arrays @@ -7,8 +7,8 @@ import { readFile, currentDirectory } from '@/utils/file.js' */ export const fileReader = (): number[][] => { // Read quiz input file - const directory = currentDirectory(import.meta.url) - const file = readFile(path.join(directory, '..', 'input.txt')) + const dir = directory(import.meta.url) + const file = readFile(path.join(dir, '..', 'input.txt')) return file .split('\n') diff --git a/src/2024/2024-12-03/main.ts b/src/2024/2024-12-03/main.ts index 9f88c1d..6eae392 100644 --- a/src/2024/2024-12-03/main.ts +++ b/src/2024/2024-12-03/main.ts @@ -1,9 +1,9 @@ import path from 'path' -import { currentDirectory, readFile } from '@/utils/file.js' +import { directory, readFile } from '@/utils/file.js' import { extractMultiply, extractMultiplyCondition } from './lib/extractMultiply.js' -const directory = currentDirectory(import.meta.url) -const input = readFile(path.join(directory, 'input.txt')) +const dir = directory(import.meta.url) +const input = readFile(path.join(dir, 'input.txt')) /** * Part 1/2 of the 2024-12-03 quiz diff --git a/src/2024/2024-12-04/main.ts b/src/2024/2024-12-04/main.ts index 77a7bc3..d12deaf 100644 --- a/src/2024/2024-12-04/main.ts +++ b/src/2024/2024-12-04/main.ts @@ -1,11 +1,11 @@ import path from 'path' -import { currentDirectory, readFile } from '@/utils/file.js' +import { directory, readFile } from '@/utils/file.js' import { wordCount } from './lib/wordCount.js' import { countMASword } from './lib/xmasCount.js' -const directory = currentDirectory(import.meta.url) +const dir = directory(import.meta.url) -const data: string[][] = readFile(path.join(directory, 'input.txt')) +const data: string[][] = readFile(path.join(dir, 'input.txt')) .split('\n') .map(row => row.split('')) From 4d1c496e5fd1d8826a1d5130baa2eace7deb43c9 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 29 Dec 2024 11:55:09 +0800 Subject: [PATCH 30/33] feat: day 10 hoof it 1/2 soln, #15 --- README.md | 2 +- src/2024/2024-12-09/main.ts | 4 ++ src/2024/2024-12-10/README.md | 16 +++++ src/2024/2024-12-10/input.txt | 8 +++ src/2024/2024-12-10/lib/scores.ts | 108 +++++++++++++++++++++++++++++ src/2024/2024-12-10/lib/types.ts | 50 +++++++++++++ src/2024/2024-12-10/lib/utils.ts | 66 ++++++++++++++++++ src/2024/2024-12-10/main.ts | 21 ++++++ src/2024/2024-12-10/sample.test.ts | 15 ++++ src/utils/file.ts | 6 +- 10 files changed, 292 insertions(+), 4 deletions(-) create mode 100644 src/2024/2024-12-10/README.md create mode 100644 src/2024/2024-12-10/input.txt create mode 100644 src/2024/2024-12-10/lib/scores.ts create mode 100644 src/2024/2024-12-10/lib/types.ts create mode 100644 src/2024/2024-12-10/lib/utils.ts create mode 100644 src/2024/2024-12-10/main.ts create mode 100644 src/2024/2024-12-10/sample.test.ts diff --git a/README.md b/README.md index 1b4f4da..6224055 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,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..9749642 --- /dev/null +++ b/src/2024/2024-12-10/README.md @@ -0,0 +1,16 @@ +## 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:** On-going ⭐ + +## Code + +### `utils.ts` + +- Utility and helper functions. + +### `scores.ts` + +- Finds valid hiking trails (trailheads) from a point coordinate in a 2D array starting `0` and ending in `9` symbol and calculates the scores for each trailhead. 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/scores.ts b/src/2024/2024-12-10/lib/scores.ts new file mode 100644 index 0000000..0c4d2bb --- /dev/null +++ b/src/2024/2024-12-10/lib/scores.ts @@ -0,0 +1,108 @@ +import type { Point } from '../../2024-12-08/lib/types.js' +import type { GridCoordinateSymbol, PointSteps, PointDirection, TrailScores } from './types.js' + +import { findZeroCoordinatePositions, findValidSteps } from './utils.js' + +// List of trailhead scores +const scores: Record = {} +let activeZeroIndex = '' + +/** + * 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 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 + * @returns {void} + */ +const findPaths = (pointVector: PointDirection, data: number[][]) => { + 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) + + // Count unique ending 9's that match with the starting 0 + if (pt.symbol === 9) { + 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) + } + } +} + +/** + * 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 + * @returns {TrailScores} + */ +export const countTrailScores = ( + data: number[][], + printLog: boolean = false +): 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) + } + + const total = Object + .values(scores) + .map(x => x.length) + .reduce((sum, item) => sum += item, 0) + + if (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..88775f6 --- /dev/null +++ b/src/2024/2024-12-10/lib/types.ts @@ -0,0 +1,50 @@ +/** + * 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; +} 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..8921a63 --- /dev/null +++ b/src/2024/2024-12-10/lib/utils.ts @@ -0,0 +1,66 @@ +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 { PointSteps } from './types.js' + +/** + * 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..6b3240d --- /dev/null +++ b/src/2024/2024-12-10/main.ts @@ -0,0 +1,21 @@ + +import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' +import { file } from '@/utils/file.js' + +import { countTrailScores } from './lib/scores.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, true) + console.log('Total trailhead score:', totalScore.total) +} + +quiz20241210_01() 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..86eb065 --- /dev/null +++ b/src/2024/2024-12-10/sample.test.ts @@ -0,0 +1,15 @@ +import { test, expect } from 'vitest' + +import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' +import { file } from '@/utils/file.js' + +import { countTrailScores } from './lib/scores.js' + +const input = readAOCInputFile({ + filePath: file(import.meta.url, 'input.txt'), + type: AOC_OUTPUT_TYPE.NUMBER_ARRAY_2D +}) as number[][] + +test('Defragmented disk checksum', () => { + expect(countTrailScores(input).total).toBe(17) +}) 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 From 44e154cd887d4272fd714d7bc831ed9e4f45eac5 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 29 Dec 2024 12:05:29 +0800 Subject: [PATCH 31/33] chore: commit scratch visuals --- src/2024/2024-12-10/README.md | 2 +- src/2024/2024-12-10/assets/grid_01.png | Bin 0 -> 11221 bytes src/2024/2024-12-10/assets/grid_02.png | Bin 0 -> 15431 bytes src/2024/2024-12-10/assets/grid_03.png | Bin 0 -> 12701 bytes src/2024/2024-12-10/assets/grid_04.png | Bin 0 -> 24388 bytes 5 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 src/2024/2024-12-10/assets/grid_01.png create mode 100644 src/2024/2024-12-10/assets/grid_02.png create mode 100644 src/2024/2024-12-10/assets/grid_03.png create mode 100644 src/2024/2024-12-10/assets/grid_04.png diff --git a/src/2024/2024-12-10/README.md b/src/2024/2024-12-10/README.md index 9749642..6d5a586 100644 --- a/src/2024/2024-12-10/README.md +++ b/src/2024/2024-12-10/README.md @@ -13,4 +13,4 @@ Visit the Advent of Code website for more information on this puzzle at: ### `scores.ts` -- Finds valid hiking trails (trailheads) from a point coordinate in a 2D array starting `0` and ending in `9` symbol and calculates the scores for each trailhead. +- 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. 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 0000000000000000000000000000000000000000..d5e068b762757e577c04fac95f495cf903b05bae GIT binary patch literal 11221 zcmb7qcQ~7E-+x*ikD~5UyQsbQu35EWw)R#O4T9P$=%6)X#IB+xYKyIAiW0S@M#ZW< zL#-Gwe$n2~bHDfd-tT)Hzdv#~;<{YU^Sr*}Gro}zb=Apk(BA+60AxT76@38U(kkI| z_g~itzlrCGUMKvxB9xo_xijh!O*FdT!dY-eYo-X4Rd3_ZEEIra6wbC?>_f)<=;1js=8_Y!Dn z@*es14>uX)IepN=VSgDu zga~DfJ^%m;58bz^0RVqeAUyyO#|k6@{CLC;0s!Qq*vSB4>Iv{mfS=qjaR7ixVm<&J z5^CG@`iPsFA?knv6@UiBGZ~@@e{86nymz#_Ja-^$!*5n;Xu%5tq{;reavKbwQmc*>jb)s>WJP%C9 zp7P(DDx59If!Z*Q`R|Og@SH`Pli8f80|33$zxJXEOHnpkWgd~9^3u2GWgEy$s#7Sp zvgN1Lk9=L$o@oQitKuSl)U3vQWGh5ic=W~L0g2Kbz-f!4oUw-oE# z1fCLOsZ?Ws>&3k*YX6@*jj7r zbw@-L`0v`D)QKYF;aomyc8bg+@#nO#=Ta&}<80_DK@Ocqk&+`??ql0BLG#fyS4+uB zE)g#*XmC@64kp*@?Jd_~4FE#WN+( z6Ea(!m?g*5Mp0QDup9IV z0@jB0MRJ)*4LB%K7VXQu)r2=sEZ*P8FRnd$Q-A(=l@{fRh4|A0&5sw_R+OPi_4_kO z!9Oy(oq4*E@NMH0LxCqByVn9&5tY+n#m9Lqg{_Mj#`kzB_=d)OfPG4amdhM`Uo*%_ zjLImR_hrRtv|OU}I&8-8&X(^#|9~pm>%aMhS!eR|xHZVVmcri3nK_uZ-O;!0Y*60@ zTLd2~Oo@71T3Pe)%ZL~8Z4=W0jwg&`xI?zWo&4pO9HKXY^t%Rm$=aPIQeJ$MI(_Eo zhY2dmM(F7YJ}V!`w`1TA2M*Lq8fkTucrCkPgjK8Lo94Wzpm{3S*!|Z05eLr_sgdO& zMX1U0aV-7X=_Y2tmS)ZK36Im( zowv`*8p-vvz6qKJ1X>t2JXF&~7DDQ5#@~{>;Xg)*Vd75pGZq9)OLWG!tda~Kj9I=8 zqHvfXcftSAu?4$irOg%DABby>WlvZbmCbzTIhNS3-XQsKs0Hzyt!~a3?hRcwUzM+o zkZBQPwD4`!j8CV=mZgY{r7QSw%19nWo7P*e1;Ne-ha8P&p^leFNFNUI55!t^*@WQ|6P*-kdcac9E(?eojcW%@ zZ1QqX`ML-?=+DKBi%)@hUmDH?)3!&7yrU(m3fH`70-6RKP}c_>*e|rYF|%K2(*z9B zO4Q|2b#H`Dh->qFS^fZI-G`~EvC)~avJi_$Ro_etSCrSEncx-_l)Lu%r3Si7dr6t~ zK4Rm#e4U-+a(EPd1!C$}{M?Oa$U-839t)6-Ex#yO?bU9gqzYf^UPAqQ)v)m^0}T73ZPgiGsTF7`&)ps+>S=I_$|t5N*b?)dMt z!z;nFv|(Q=JbGmd;37h4QkjPT<*@(O54pHa8#L#qkFVz~c0Gcgf7^c(+;*H!3;?*X z!}hK`M?26*d%kukch|5#{CG%yX@C&bTil;z2*uyO zp0DKDk9E%JVcOVpQYMNj3jM(%+6TkYCPte6{QNF+2Y@HnVdA0>m1-qNPC0jirDxK^?fZLVJ)eg4s{h%{{@aNVN;ip2ppMG{A>1C5Iz5@7{T+mn#aUBV?4@6d zyh{Mvf0jij!FR=@G-t71K9X>zE!ng75huV5+$Y zm-Bxo;_wRgEXy1m?IqM5J+V1ygg$botowTo;$vzfHG3)~ zO6FY%i5R=3jq?2p%focqJ-~3zk0E0_f~Ft0IBEIN$Nf7>3g7~KbLJ~LmwWN)1-p9m2FgtZ8-;GxKRXd3q@^)PRjN;x!_=*i1!ZNG`=43#L&RMV~7 zIPc9LZzO?YBpH`It?~I0f-V{2RUW7ihj~1g&`FgXj zNty}xm^9xz=K0 zc3{|PS72|S7d&-)+u2m)x%cTJiN7S`_LoMsH30U~dG@4m@?=8LsVMtIa;zM2aDsiH zXp13Ubs1qL}*eD^c9a`KF><&Y^yU8^MeHkEdJ87LbN5~HEv zS%jJQb2Shs%Ai595y#9X#fro$5K4+<0qHu!`j<4&h%8`>?Y{%h3J=o6?%HsB00pW| zWa~Ywv&jRSQ}l!odjBqgf6L0>D0NAyibXZH`MQ^`?n6-@h9!-sCo=Gs)V*X#;qn}; z*lZ^du&NAGi-8?AT^@unu6owg+L9^Wmv;f88Mp4D0& zPoDNeA^B~i3VVCoXF(kONkwDbKa;h>Zob3Th`Lr>*=Pht*GOmuL7>B5wz!1C=Mz%K zh`Iy`h44=31Jj(L0`ZaPy=*TVJyid`MIBb(CvPXCcy;76YB zciXE7#WwPLjb;L}H{A!h13}qpI(?}Q?Wy$G!?{!asp0&J6_Pm3mG+$HG7IkU<;h9n zEH$69w{SuN6>j(r9}%}xrlIXZtM$~v6>!o*Xy%?V0_Wwap5HxDTyljWUai>W7$~5u zwe1f-`E|_D7)s)+9JHiSX7+|QnQb94$+oM7TO-{Dl!?{dkLny2KAVXI2$-!FSR;b7 z!8t`%yyn*}!3Jh@v0SF5cBR+Xg4hGu;?(cikl)qvz`8{bI0@ZkpsO#z8uI$d3%I;i zK%A1z405_SOw=^$>;OBS*rzSf4#K`U!sfk!;7XC+LIY!KpES33FsRS8A%^q^NG!y_Xl{e0xh(!${SFDEotd)%?O0kD+PAYgx06$ zUyHBNKog66n+@nM7!vms~6 zhdzo|JFGbz002H+(m*Fxz;p1 z19y2?tW{0?4F%~-x*$z7u0!GP<=xc%z452o#}#U3A0RrLuR8KS`Q0f?KAFdh6G$7= z`3D9^mmWfPP)7}{rE6FR#$gY)`}`B)Tb?(L!=8yIZ2Bf7)qzxs4u4#cegtGAQe_{u z~H?Wm_&>;NCQgqmbaCN?&kXQZerlww!>=ode1S)oICM{5*{ zu@H`W&smj3oQq%fkmMtfOl^cua9@Q)?{?1N3C|LL#Z6u-Gf>7lO(pQO~`e?D!^&SzQKf{8rqHeJEO|W)=8VSuN5hKg+2T zO8C%JPR=6{8?%pZEhXqQnuWh%S%N(-muY;fX~bSNSIyHZIe$6+t^Oq&hP{v9^+s0! zHk;lv0F!7Y?a0%EWY6! z3K1cvgVsXw_O|J(Rg|dVuYC4*AqDVD{+bToOE$A)?i1}^YJ6;~Z;1Yd0d-@Zk1KQh zB?x?M6e)Dg*t`}m{jst6r6&FEi-2!H4%&9QnAsRt2BPWv|0}LA5CM@at#rFXA4f6x z+=cps!=7CfzA7UJLd|~B`u-K`_Vq+6P(r7*7vyPGPL29n5JO%E)>K2tn&HO{0us_! zbxBmo9ilscWmz|h*O^I}!zb2^G@ji(6P61byC!^rkF;Fi3$Arnygb>TPIpZekoJ66 zD%P-9@+>M59}G04|4uW+K={Xegj=Q0x~~zxZl}N&rcPH8ZL25d8Y~eA>ywX$^Te$_ zZR+OR7=Q@@Ks{q+!Q)rn6@`FGeWhmPJ!(zhIi}v!BSCj~)zhyi4SbkUhFzRv7}-p+o5~*X z{Z>~Ee3)gZu$_e4OuShbaQGU7?0;c|aSbjoo=`jr>*_*m%=j6)rMyti#=S;{YSadw zs$=wgVV@5Q)vK|lLY@Un&#=gXx-pCT`GQ*6&F;jZgKF7usm1glrori3&6%&<>m}D~ z!xyIKEV1M^P5im_zD8C*@wuWugH7>z3XrAl-R?8ZdxTwHkc{W!a|*y2HwT8E4@;hO z(%<2y5TSed;X0S4Mg{K)qRyVd*qbrBmR8wk>BGT=5AdsoCu60Ri*eW|3k)0YSb;)G zK0(---0A^c<2W4+W8SP4$yx>O=b=YAI4K`p1syq_byG%6lX7EA6U*x1$sX{SQtvyk z!LdMB(lUKlYgDwEESz}+w|QxusM8vMM7|Dww?8>LRrRn@NDlIXUH)pPhaD+b=Vw>j z27;g$=lKXYk-Q4I-ckr2@3GM?D|%PZVw6$Vww#|8s?E`}%;ZIzxQs z9Fq%nIs$@SN@t|fd5&F+;g{HBH8KB0PQG%1W|qY_tq9T zo+?ODh#tkX5??ji&WZl8k|IYJ5Ts%6*u!3(ul67-M57H6siy2kKk{kga7UUhpwg{F z8ES%@$V=EzmI8d9%>9+cuyfsGF#;s|x0#p;@@qWPy{8vdBn5IG zTi)T=c@OFSR*~&2MvON#R2brQ32o8CGqh|s_%PUzolk1d0EJSqnYirJzLzKme{3d3 zNT5Hl$7yExtQaAOz=|QXC5Rpg2n>v;g6^HBA8xsXBL>ex6n?%Q?L!V6M5nXA{x__L z>7h+v)6c*M)|PA;3XWWyUT)imtNry`HrzX%icdqg+NTnAWe~yAAqpO=Z<K%9{zODUUgfLw{E3?q;``Oz54(H%n7dsH~8f#>OvjK_&5%G+A6U%X_)(Sic=V0bklaltz z6<@r+VOd2Yc&KB(vbn+l-#_1Iea<)^j;uP%aloeXeeoODU1M8PZRltbon(~}LgBt6 z2gE32!8k5ZmD4jNdaO?v>%P3GWM7JT6S1sF1f~7sTHz@u>FtDv!rZL{y-rZd`c2J< z)l`-Ldm~;<@#K~cR9uYty{EX@)1PgRnY^rk2ZrOx-+v^T%yy>+SzwDHMrR9`%w|%8 zRVB*~U0yqEF^?O0^mX7S``rJLyaCm80$P1HNv95*7sVSx?g;UxNFu5vV_?eHF?_u4KabG|##jwo^qyqP0 zp;g~j=v}$hEz5^h>i2lDgA_}(6Nu)zr@R*Tp>w(jXn?VyspW3q{ssp@- z$QLW}=aX^B>}=20VeejK-FhzWMZJ!3Uhralj1B94g2T>E2wryX+t{h-_WDbA8*at# z4mST<+z7(?eId$!ZJEisi0>wl+j7WBaqagRs|n6vE=B|=iHS2)I5-&Bsf_zb*9BKHqRM!`(?)=$S zfGx1IujP!Pk2!QGzpYlm;;GnLf+3VTj~zWd*=QEA2f_4Lw_nFt%!KGzM=gRTYwW^H z-tki(iF=+u{+f)^+)9(yoa+yn|J2mrQ6Dkid_vy4O`BqpyWC;YqTIeM`;>q9=i-vq z=LUB>ha*ukKP1%0G~^jj?L6pt#JBh+Fw{AxtHH_6*=YgWGg?uTqohT%Ra{b*dRBCY z_*w%T4$|qCW$(beEq(P+NU-js8b)XGYB4I%K}9p=<1sH$dgQ9xldSG?l~SpnE2hIm z!B#B+>dNLKY5%%lw68Lmn|=@nhqVPS&tax;ASD4S+#Ys(pS&xfaLF0vSAV=Q@C4E| zz1ZDXT~^}%u%X3ufLYP3QBM~nbYGA9nJ5ZLwGOcjNxi8mCmv4tY|1CW^`jGBkDsm34Y-~FQ zP$lz>?mp8-%qIWhz<{Pa(Gp-=Y$2t%F!Lu3v}5b{XEHkj_4F~=fF?k7wRGDv*GxDmc%#(E$;ls4ZKXU#IVNWpElyL^>3Fhj1!t~Q}Y{7YIj^@9eS8&N=``>kJ2sw z2r{ZTj|&z(fdujT;_LdgMf)emVILY8%&4*jspXgJ*&r=0|)-`a4xM#~bLT%YI z)hW_81rJ{}Uk+B!gz)sKo^QcO&y5>-4^DAjJEu(+S+12P?gQS773&<*Q&qOF3XB%v!Vl25P2zFOwE(~{*mmr%h z>2M_zBs1X%u}UzhzxrEV&=ZE1MBfYiKmZZF)P#Hg!CO=P6EAm3Cp>)k;y$0JCp(w; zzV0|6e)R&RCeIeAD=B^ll>oygujUkfTn%<|!V=|33sjyJAgk-ig|xEAi5`MSK6W1$6Qr zq;$YJXo~{;(a)evvc6q0sk8=P9jfUrQruFm9#}+!+Un&$NYTQy5ajXkk^HHWVz>ch z*Y*qKhFc~lCKW{2MT=Sk@?oMPM zh5usMHDXNA1xdDVMC#$pzm8u4d?rY_zf>Heu#J>6_U6FE&IUy}9z1fp;aZbpvTLEh zp#PE5>vuIwA_+Wp3GoY8EZ`j*JX2~#*W#NC2y$ruzLsjA@wmAGYqQh!Ir7U&IG`6y zFbNu3W6FlZdUP5+-Bo?`0D3sHGAteceEsT)%Ud{HumoZ2O?NXfD)5Vub|8g7kgXO@`CCPd-kxh{bs=eU;rrT7Ue|9BJW!#Wn ziTXDfBS@|$pvgt%gy&rjWc~wOo8~zpJDtRI6`L6Z4c8{L$MY(Bi$)KhBWS(~n z%jmO;HXCzpg<>bx09?&r^q(SczbMa7t6^HXTcJ;xL^7_yDPm?xJ3Z8Dp_hJ1bdP=a z|0@ej$SUPMZ!pRrVBbQ__qsfHGKwlz%-eR#jPlj9;JilvU>u@j$r+hfyr0y|hS#!M#qU4%u2Q;?0EKCj zl&UlaM(JAOuZ59zwuxv*8I=EWIR3J5{)dnMKjqZN)86{TogUZhrP(Gr!~Ej^GWvfP zqYH6g;$*QWuS8gnXxgCMyI#!<^x?1s&sljjNQOqSR&8p5C$ECUHPh zW!N`Mexjjk+o9XRDUslzh!g3dX$oVVwJBePW+iveodFKd533VCYY9m3@_uUn+ zsyv&e#AKUBe#Nq^`lE}c`4tNwO0hB_Z_MiKsw6WrHqxQ?2a1BXP%e)LUb&tu)G_O+Zpv4qRTGu^)H<~E?P6NzLP6jb$-!3b8sQp>Sl-le}d_U&Z&`Y zlt+8g>8dE%5mH_J9Wln=YF=rf>Go8!8XH^M*85_NDyEp7HcG?6r+LrhYAK3fNUt7W zAp@kx5Kv9Z1**|IT1pHVE`nq6kI1}e0F{x^>=3nite>aEjmx;)M23;WDR*qpr)RpS zYn5tlXLiulm@_v+!UzY!vLlp(q;+=4MkB)I%BWON16q#k^~8gnuc`@@ zltlmWm`inV$dLq%um}k%jO4h7$x3m1~R5?za&*IMc%i7wb_kcU#C4n8WwFe#_7( zYR6>vMK$n&Tls)dXwAHc%ujnSs82$--dJ~YsBa_AE7a2xCqGnpYCvfX|G!u=&;;Fr zk;nK>gLOM7%#alZ5jB2fGoIt0w(eu6pB;;g|Aek155Dkkk93{o`Z*sAM;tbU**J?m zmuJ(*C1>uesAI=Gg9;w>JI8Io5gq?_MhTBy=e;3eEk9i-0(v2-*5CCUmq_38gPx~~ zN%T3x4+JlK1xiWJDPxoUp3IAQu*Y+4opDEV`UZ5(6VC+zxIg|~lJ5cInw(ybCy2K2 zEe=mJ)v!sKaG5wA&qw^tRO8$c(q&<^e`8jVEOdr$bLoGi8v$APQ=t{fJr_q!9tVlF zD0OliO$F7{*Kda02YlxL@SDZkNaHTVn}Tm@vEV>U>$E-Dfnc-7Mx&sEGt{NRDIa~j z0dJgp>8GG%e)<}kbjHF;8hJ*7`TY-`+dr1)iRfwyquRb{ifV^_Wjbt~UUHE$LGI9O z8L8YYKgIX>(boj51f^Ct8@I~$d9HZj25=*riK#Wqv*GrHGO|_#Z-%Sa6*X<1v6~Lh z|1F_~FO6dinYS3mrmu%>B%Ju6&ksL!#xAScV?X5Q?Pm1R%eGL!7>)N`b8^>t%w*Ad z6`+s2^s5q{%1nVL0c1x#`Qi<-`xf3-t!xMwm%c$M0ekC7$R~dba+jQE+B2Wj?ys zIT&s~D#AaIYP;@F`<0n8Xxrq`Dn&w?yph7n(hk`1mgUe$vMm8HqM~$;jlxj_iLtNP z^Ba%X_HZ=^h*m@RUyXsl*n90b79JZlY6K-;d62OFYgJkt(2aH~)c;?RzYDb2A5Vf` zx@#aZ^_Oz{=lIh<8sWbjHC}mOV5=Rp60N*#=BM-7Wv9cY#WME|&A$Mmmy<8%z!r}y z-?yO6!G2&HwyRr~!)ILu8g3yiI1XIX2IP}PqtiaMR1!h|JCJZ6^~K`V7usaEoNe5` zre2moOgZ7?1P7WvT3r_;xdHI{Z3k--Na)W%@J(zLUyVt&zsAjXEhP|(tm(4JN_EMm zfik$1T(GiQ^=pRQhst``A?xvRu9~TxeX-cF&vl-;wRow89x(={v5+Ti%!s=7K zVS*DOrW1YV4#o=du5@gIq{A_Ne z*@mLQ?iV1KjBrG~@*f*7@B=(?&x*Ns#D5+2fZjlsN9e@gupOBrd`9c`W=Hueqg<{n z?@D3O)A_|LG0`=f507q#k^+8^{houv_&M?9B z1mzIhFZ&PeiGNZF(wtu&@&I21ZQJU=e3tRO&Ode~lv)VW2Tlq$0h&uQ1Bp544m-{~ zY~-r9aC8Tu&&3HPJr7;GXp$tq1_upyNKnDWHD>sHTr5y1*u8DI@YeYe?!kKAiVdOL zMc}SbGyjENv(Y~A?lU}UPx6*TU?Vc$cw%lcdS|W{UxO^x%eXQEwF}j#FKUBkGc_NEC(~G9%3QNvD;jA|m%CP0ru~i{0%|<~V$j>w+ zAq1V960zOaCIUzc`W;X^z~dB4XeBaOGq-W~{hh!f38w3L#Z_u-TZf627q;IIwVve3 zIAf5voZt6YKE$J9Z*Pdf=#;1Oe8u&9j*~nj0AVS8MLi6E8E=$OWFUcczpG8$c{9y9 zz<-D2my=Y9h)Nc(TInGSDtxVn$Uip9KyUBxXBE2^JcZkC3AN;bF`b>dDUvmiAyZ1ZJG%jAv q?LR%u|9vL)-@KV{Gpy_UdQy$l+5y8!Bw-X6094ggDN}y@;{O4Qyg*j~ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..b87edf4bae1eccf4ad073fe07ad939982e795cb7 GIT binary patch literal 15431 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 SxB}$+KcKQIG6m94-~A6Di@ps2 literal 0 HcmV?d00001 From adb9f663acb34f5cf50ec2aa60f092a7612c8178 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 29 Dec 2024 12:44:51 +0800 Subject: [PATCH 32/33] feat: day 10 hoof it 2/2 soln, #15 --- README.md | 1 + src/2024/2024-12-10/README.md | 8 ++-- .../lib/{scores.ts => scoresRatings.ts} | 44 +++++++++---------- src/2024/2024-12-10/lib/types.ts | 10 +++++ src/2024/2024-12-10/lib/utils.ts | 15 ++++++- src/2024/2024-12-10/main.ts | 21 ++++++++- src/2024/2024-12-10/sample.test.ts | 14 ++++-- 7 files changed, 80 insertions(+), 33 deletions(-) rename src/2024/2024-12-10/lib/{scores.ts => scoresRatings.ts} (69%) diff --git a/README.md b/README.md index 6224055..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) diff --git a/src/2024/2024-12-10/README.md b/src/2024/2024-12-10/README.md index 6d5a586..5faaddb 100644 --- a/src/2024/2024-12-10/README.md +++ b/src/2024/2024-12-10/README.md @@ -3,7 +3,7 @@ Visit the Advent of Code website for more information on this puzzle at: **Source:** https://adventofcode.com/2024/day/10
-**Status:** On-going ⭐ +**Status:** Complete ⭐⭐ ## Code @@ -11,6 +11,8 @@ Visit the Advent of Code website for more information on this puzzle at: - Utility and helper functions. -### `scores.ts` +### `scoresRatings.ts` -- 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. +- `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/lib/scores.ts b/src/2024/2024-12-10/lib/scoresRatings.ts similarity index 69% rename from src/2024/2024-12-10/lib/scores.ts rename to src/2024/2024-12-10/lib/scoresRatings.ts index 0c4d2bb..1865cf3 100644 --- a/src/2024/2024-12-10/lib/scores.ts +++ b/src/2024/2024-12-10/lib/scoresRatings.ts @@ -1,33 +1,25 @@ import type { Point } from '../../2024-12-08/lib/types.js' -import type { GridCoordinateSymbol, PointSteps, PointDirection, TrailScores } from './types.js' +import type { InputOptions, PointSteps, PointDirection, TrailScores } from './types.js' -import { findZeroCoordinatePositions, findValidSteps } from './utils.js' +import { + findValidSteps, + findZeroCoordinatePositions, + getCoordinateSymbol +} from './utils.js' // List of trailhead scores const scores: Record = {} let activeZeroIndex = '' -/** - * 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 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[][]) => { +const findPaths = (pointVector: PointDirection, data: number[][], isRating: boolean = false) => { const grid = { length: data.length, width: data[0]!.length } @@ -41,8 +33,12 @@ const findPaths = (pointVector: PointDirection, data: number[][]) => { // Count unique ending 9's that match with the starting 0 if (pt.symbol === 9) { - if (!scores[activeZeroIndex]!.includes(pt.coordinate)) { + if (isRating) { scores[activeZeroIndex]?.push(pt.coordinate) + } else { + if (!scores[activeZeroIndex]!.includes(pt.coordinate)) { + scores[activeZeroIndex]?.push(pt.coordinate) + } } } @@ -52,7 +48,7 @@ const findPaths = (pointVector: PointDirection, data: number[][]) => { validSteps: findValidSteps(step as Point, grid, data) as PointSteps[] } - findPaths(point, data) + findPaths(point, data, isRating) } } } @@ -61,12 +57,12 @@ const findPaths = (pointVector: PointDirection, data: number[][]) => { * 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[][], - printLog: boolean = false -): TrailScores => { +export const countTrailScores = (data: number[][], params?: InputOptions): TrailScores => { // Find starting positions const starts = findZeroCoordinatePositions(data) @@ -85,7 +81,7 @@ export const countTrailScores = ( activeZeroIndex = pt.coordinate scores[activeZeroIndex] = [] - findPaths(initStep, data) + findPaths(initStep, data, params?.isRating) } const total = Object @@ -93,7 +89,7 @@ export const countTrailScores = ( .map(x => x.length) .reduce((sum, item) => sum += item, 0) - if (printLog) { + if (params?.printLog) { for (const key in scores) { console.log(`[${key}]: ${scores[key]?.length} score`) } diff --git a/src/2024/2024-12-10/lib/types.ts b/src/2024/2024-12-10/lib/types.ts index 88775f6..5318951 100644 --- a/src/2024/2024-12-10/lib/types.ts +++ b/src/2024/2024-12-10/lib/types.ts @@ -48,3 +48,13 @@ 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 index 8921a63..dcb90f6 100644 --- a/src/2024/2024-12-10/lib/utils.ts +++ b/src/2024/2024-12-10/lib/utils.ts @@ -1,6 +1,19 @@ 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 { PointSteps } from './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 diff --git a/src/2024/2024-12-10/main.ts b/src/2024/2024-12-10/main.ts index 6b3240d..13621d6 100644 --- a/src/2024/2024-12-10/main.ts +++ b/src/2024/2024-12-10/main.ts @@ -2,7 +2,7 @@ import { readAOCInputFile, AOC_OUTPUT_TYPE } from '@/utils/aocInputFile.js' import { file } from '@/utils/file.js' -import { countTrailScores } from './lib/scores.js' +import { countTrailScores } from './lib/scoresRatings.js' const input = readAOCInputFile({ filePath: file(import.meta.url, 'input.txt'), @@ -14,8 +14,25 @@ const input = readAOCInputFile({ * Counts the total trailhead scores */ const quiz20241210_01 = () => { - const totalScore = countTrailScores(input, true) + 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 index 86eb065..8b456c8 100644 --- a/src/2024/2024-12-10/sample.test.ts +++ b/src/2024/2024-12-10/sample.test.ts @@ -3,13 +3,21 @@ import { test, expect } from 'vitest' import { AOC_OUTPUT_TYPE, readAOCInputFile } from '@/utils/aocInputFile.js' import { file } from '@/utils/file.js' -import { countTrailScores } from './lib/scores.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('Defragmented disk checksum', () => { - expect(countTrailScores(input).total).toBe(17) +test('Total trailhead score:', () => { + expect( + countTrailScores(input).total + ).toBe(17) +}) + +test('Trailhead rating:', () => { + expect( + countTrailScores(input, { isRating: true }).total + ).toBe(25) }) From 92b82481ad451106e6f131bdb3ae179ee5cc9ee3 Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 29 Dec 2024 12:49:19 +0800 Subject: [PATCH 33/33] chore: update comment --- src/2024/2024-12-10/lib/scoresRatings.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/2024/2024-12-10/lib/scoresRatings.ts b/src/2024/2024-12-10/lib/scoresRatings.ts index 1865cf3..8d3176a 100644 --- a/src/2024/2024-12-10/lib/scoresRatings.ts +++ b/src/2024/2024-12-10/lib/scoresRatings.ts @@ -31,11 +31,12 @@ const findPaths = (pointVector: PointDirection, data: number[][], isRating: bool if (step === undefined) continue const pt = getCoordinateSymbol(step, data) - // Count unique ending 9's that match with the starting 0 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) }