Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

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

Expand All @@ -14,6 +16,7 @@ This repository contains solutions and a local development environment for the [
- 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)

</details>

Expand Down Expand Up @@ -110,7 +113,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

Expand Down
22 changes: 22 additions & 0 deletions src/2024/2024-12-08/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Day 8: Resonant Collinearity

Visit the Advent of Code website for more information on this puzzle at:

**Source:** https://adventofcode.com/2024/day/8<br>
**Status:** Complete ⭐⭐

## 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

### `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
12 changes: 12 additions & 0 deletions src/2024/2024-12-08/input.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
............
........x...
.....x......
.......x....
....x.......
......B.....
............
............
........B...
.........B..
............
............
105 changes: 105 additions & 0 deletions src/2024/2024-12-08/lib/GridAntinodes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
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 (y,x) coordinates */
antennas: Antenna[] = []

/** List of unique Antinode (y,x) coordinate strings */
antinodes = new Set<string>()

/** 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

/**
* 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) {
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 === '.') {
printBoard[coord[0] as number]![coord[1] as number] = '#'
}
}

console.log(printBoard.map(row => row.join(' ')))
} else {
console.log(this.board.map(row => row.join(' ')))
}
}
}
93 changes: 93 additions & 0 deletions src/2024/2024-12-08/lib/allAntinodes.ts
Original file line number Diff line number Diff line change
@@ -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<string>} All `Antinode` (y,x) coordinates along the path
*/
const getAntinodesInPath = (
point: Point,
increments: Point,
direction: number,
board: { length: number, width: number }
): Set<string> => {
const antinodes = new Set<string>()
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
}
8 changes: 8 additions & 0 deletions src/2024/2024-12-08/lib/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface Point {
x: number;
y: number;
}

export interface Antenna extends Point {
frequency: string;
}
60 changes: 60 additions & 0 deletions src/2024/2024-12-08/lib/uniqueAntinodes.ts
Original file line number Diff line number Diff line change
@@ -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
}
32 changes: 32 additions & 0 deletions src/2024/2024-12-08/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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'
import { countAllAntinodes } from './lib/allAntinodes.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)
}

/**
* 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()
21 changes: 21 additions & 0 deletions src/2024/2024-12-08/sample.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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'
import { countAllAntinodes } from './lib/allAntinodes.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)
})

test('All antinodes in line', () => {
expect(countAllAntinodes(input)).toBe(34)
})
Loading