diff --git a/Dockerfile b/Dockerfile
index 9d30af8..ac6aa28 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -26,7 +26,6 @@ FROM base AS production
ENV NODE_ENV=production
COPY --from=build /opt/app/dist /opt/app/dist
COPY package*.json ./
-RUN npm install && npm prune --production
USER user
EXPOSE 9229
CMD ["sh"]
diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml
index d3ed5a9..0d16fdf 100644
--- a/docker-compose.prod.yml
+++ b/docker-compose.prod.yml
@@ -7,7 +7,6 @@ services:
dockerfile: Dockerfile
target: production
volumes:
- - ./output:/opt/app/output
- /opt/app/node_modules
ports:
- "9229:9229"
diff --git a/src/2024/2024-12-01/README.md b/src/2024/2024-12-01/README.md
index aef2b62..ee7b98b 100644
--- a/src/2024/2024-12-01/README.md
+++ b/src/2024/2024-12-01/README.md
@@ -4,3 +4,11 @@ Visit the Advent of Code website for more information on this puzzle at:
**Source:** https://adventofcode.com/2024/day/1
**Status:** Complete ⭐⭐
+
+
+
+| Code | Description |
+| --- | --- |
+| **listTotalDistance.ts** | Calculates the total distance between the smallest value-pairs from array `a` and array `b`. |
+| **similarityScore.ts** | Calculates the similarity scores of elements in array `a` with array `b` by multiplying each element in array `a` with the total number of its duplicates in array `b`. |
+| **fileReader.ts** | Reads a two-column (2) number input separated by three (3) space characters from a text file. |
\ No newline at end of file
diff --git a/src/2024/2024-12-01/lib/fileReader.ts b/src/2024/2024-12-01/lib/fileReader.ts
index ef1c371..f3aa32a 100644
--- a/src/2024/2024-12-01/lib/fileReader.ts
+++ b/src/2024/2024-12-01/lib/fileReader.ts
@@ -24,7 +24,6 @@ export const fileReader = (): arrayLists => {
const pair = item.split(' ')
if (pair[0] === undefined || pair[1] === undefined) {
- console.log(pair)
throw new Error('Undefined value')
}
diff --git a/src/2024/2024-12-02/README.md b/src/2024/2024-12-02/README.md
index c524b39..da71723 100644
--- a/src/2024/2024-12-02/README.md
+++ b/src/2024/2024-12-02/README.md
@@ -4,3 +4,10 @@ Visit the Advent of Code website for more information on this puzzle at:
**Source:** https://adventofcode.com/2024/day/2
**Status:** Complete ⭐⭐
+
+
+
+| Code | Description |
+| --- | --- |
+| **countSafeReports.ts** | Contains functions for counting the number of "safe" reports with or without a problem dampener. |
+| **fileReader.ts** | Reads the quiz's input file into two (2) string arrays |
\ No newline at end of file
diff --git a/src/2024/2024-12-03/README.md b/src/2024/2024-12-03/README.md
index 614782b..d1b8226 100644
--- a/src/2024/2024-12-03/README.md
+++ b/src/2024/2024-12-03/README.md
@@ -3,4 +3,10 @@
Visit the Advent of Code website for more information on this puzzle at:
**Source:** https://adventofcode.com/2024/day/3
-**Status:** Completed ⭐⭐
+**Status:** Complete ⭐⭐
+
+
+
+| Code | Description |
+| --- | --- |
+| **extractMultiply.ts** | Contains functions that parse and sum the multiplication result of `mul(x,y)` sequences from corrupted (garbled) text. |
\ No newline at end of file
diff --git a/src/2024/2024-12-04/README.md b/src/2024/2024-12-04/README.md
new file mode 100644
index 0000000..7cea49e
--- /dev/null
+++ b/src/2024/2024-12-04/README.md
@@ -0,0 +1,14 @@
+## Day 4: Ceres Search
+
+Visit the Advent of Code website for more information on this puzzle at:
+
+**Source:** https://adventofcode.com/2024/day/4
+**Status:** Complete ⭐⭐
+
+
+
+| Code | Description |
+| --- | --- |
+| **wordCheckerUtils.ts** | Contains functions for checking the existence of a sequence of characters (words) vertically, horizontally, and diagonally in a 2x2 character array. |
+| **wordCount.ts** | Counts the number of occurrences of a word in a 2x2 character array. |
+| **xmasCount.ts** | Counts the number of (2x) crossed "MAS" words overlapping at the letter "A" a 2x2 character array. |
diff --git a/src/2024/2024-12-04/input.txt b/src/2024/2024-12-04/input.txt
new file mode 100644
index 0000000..90ebe63
--- /dev/null
+++ b/src/2024/2024-12-04/input.txt
@@ -0,0 +1,9 @@
+SAMPLEONLY
+REPLACEITW
+ACTUALAOC.
+INPUTEXT..
+ABCDEFGHIJ
+XMASXMASXM
+GMUMSHSGSH
+MXAXAXMARD
+ASHSGJM2MX
\ No newline at end of file
diff --git a/src/2024/2024-12-04/lib/wordCheckerUtils.ts b/src/2024/2024-12-04/lib/wordCheckerUtils.ts
new file mode 100644
index 0000000..babaae6
--- /dev/null
+++ b/src/2024/2024-12-04/lib/wordCheckerUtils.ts
@@ -0,0 +1,105 @@
+export type CharacterArray = string[][]
+
+interface CoordinateBase {
+ x: number;
+ y: number;
+}
+
+interface Coordinate extends CoordinateBase {
+ direction: 1 | -1;
+}
+
+interface CoordinateData {
+ xDirection: 1 | -1;
+ yDirection: 1 | -1;
+ data: CharacterArray
+}
+
+/**
+ * Finds the `WORD` input parameter in the vertical directions (up/down) from an (x,y) coordinate
+ * @typedef coord {Coordinate} Coordinate object
+ * @param coord.x {number} x coordinate (array x index)
+ * @param coord.y {number} y coordinate (array y index)
+ * @param coord.direction {number} Up or down letter-checking direction
+ * - `-1` for "up" going to (0,0)
+ * - `1` for "down" going to (N,N) from the `coord`
+ * @param fullData {CharacterArray} 2D array of strings consisting of letters per item
+ * @param WORD {string} Word to find
+ * @returns {boolean} Flag indicating the existence of the `WORD`
+ */
+export const checkVertical = (coord: Coordinate, fullData: CharacterArray, WORD: string): boolean => {
+ let yIndex = coord.y
+ let subWord = ''
+
+ if (!Array.isArray(fullData)) return false
+
+ for (let i = 0; i < WORD.length; i += 1) {
+ if (yIndex < 0) break
+ if (yIndex >= fullData.length) break
+
+ subWord += (fullData[yIndex])?.[coord.x]
+ yIndex += (coord.direction * 1)
+ }
+
+ return subWord === WORD
+}
+
+/**
+ * Finds the `WORD` input parameter in the horizontal directions (left/right) from an (x,y) coordinate
+ * @typedef coord {Coordinate} Coordinate object
+ * @param coord.x {number} x coordinate (array x index)
+ * @param coord.y {number} y coordinate (array y index)
+ * @param coord.direction {number} `-1` for "left" going to (0,0) or `1` for "right" going to (N,N) from the `coord`
+ * @param rowData {string[]} Array of row-wise letters from a 2D array
+ * @returns {boolean} Flag indicating the existence of the `WORD`
+ * @param WORD {string} Word to find
+ */
+export const checkHorizontal = (coord: Coordinate, rowData: string[], WORD: string): boolean => {
+ let xIndex = coord.x
+ let subWord = ''
+
+ if (!Array.isArray(rowData)) return false
+
+ for (let i = 0; i < WORD.length; i += 1) {
+ if (xIndex < 0) break
+ if (xIndex >= rowData.length) break
+
+ subWord += rowData[xIndex]
+ xIndex += (coord.direction * 1)
+ }
+
+ return subWord === WORD
+}
+
+/**
+ * Finds the `WORD` input parameter in the horizontal directions from an (x,y) coordinate
+ * @typedef coord {Coordinate} Coordinate object
+ * @param coord.x {number} x coordinate (array x index)
+ * @param coord.y {number} y coordinate (array y index)
+ * @typedef param {CoordinateData} Direction and data object
+ * @param param.xDirection {number} `-1` for "left" going to (0,0) or `1` for "right" going to (N,N) from the `coord`
+ * @param param.yDirection {number} `-1` for "up" going to (0,0) or `1` for "down" going to (N,N) from the `coord`
+ * @param param.data {CharacterArray} 2D string array containing the input text
+ * @returns {boolean} Flag indicating the existence of the `WORD`
+ * @param WORD {string} Word to find
+ */
+export const checkDiagonal = (coord: CoordinateBase, param: CoordinateData, WORD: string): boolean => {
+ let xIndex = coord.x
+ let yIndex = coord.y
+ let subWord = ''
+
+ if (!Array.isArray(param.data)) return false
+ if (param.data[0] === undefined) return false
+
+ for (let i = 0; i < WORD.length; i += 1) {
+ if (xIndex < 0 || xIndex >= param.data[0].length) break
+ if (yIndex < 0 || yIndex >= param.data.length) break
+
+ subWord += (param.data[yIndex])?.[xIndex]
+
+ xIndex += (param.xDirection * 1)
+ yIndex += (param.yDirection * 1)
+ }
+
+ return subWord === WORD
+}
diff --git a/src/2024/2024-12-04/lib/wordCount.ts b/src/2024/2024-12-04/lib/wordCount.ts
new file mode 100644
index 0000000..463aeb3
--- /dev/null
+++ b/src/2024/2024-12-04/lib/wordCount.ts
@@ -0,0 +1,82 @@
+import { checkVertical, checkHorizontal, checkDiagonal } from './wordCheckerUtils.js'
+
+/**
+ * Counts the number of occurence of the `WORD` from a set of input
+ * @param data {CharacterArray} 2D string array containing the input text
+ * @param WORD_TO_FIND {string} Word to find
+ * @returns {number} Number of `WORD`s
+ */
+export const wordCount = (data: string[][], WORD_TO_FIND: string): number => {
+ let xmasCount = 0
+ const firstLetter = WORD_TO_FIND[0]
+
+ for (let y = 0; y < data.length; y += 1) {
+ const row = data[y]
+
+ if (!Array.isArray(row)) continue
+
+ if (row.filter(
+ item => typeof item !== 'string').length > 0
+ ) continue
+
+ for (let x = 0; x < row.length; x += 1) {
+ const letter = row[x]
+
+ if (letter !== firstLetter) continue
+
+ // Check WORD_TO_FIND down
+ if (checkVertical(
+ { x, y, direction: 1 }, data, WORD_TO_FIND)) xmasCount += 1
+
+ // Check WORD_TO_FIND up
+ if (checkVertical(
+ { x, y, direction: -1 }, data, WORD_TO_FIND)) xmasCount += 1
+
+ // Check WORD_TO_FIND right
+ if (checkHorizontal(
+ { x, y, direction: 1 }, row, WORD_TO_FIND)) xmasCount += 1
+
+ // Check WORD_TO_FIND left
+ if (checkHorizontal(
+ { x, y, direction: -1 }, row, WORD_TO_FIND)) xmasCount += 1
+
+ // Check WORD_TO_FIND diagonally right-up
+ if (checkDiagonal(
+ { x, y },
+ { xDirection: 1, yDirection: -1, data },
+ WORD_TO_FIND
+ )) {
+ xmasCount += 1
+ }
+
+ // Check WORD_TO_FIND diagonally right-down
+ if (checkDiagonal(
+ { x, y },
+ { xDirection: 1, yDirection: 1, data },
+ WORD_TO_FIND
+ )) {
+ xmasCount += 1
+ }
+
+ // Check WORD_TO_FIND diagonally left-up
+ if (checkDiagonal(
+ { x, y },
+ { xDirection: -1, yDirection: -1, data },
+ WORD_TO_FIND
+ )) {
+ xmasCount += 1
+ }
+
+ // Check WORD_TO_FIND diagonally left-down
+ if (checkDiagonal(
+ { x, y },
+ { xDirection: -1, yDirection: 1, data },
+ WORD_TO_FIND
+ )) {
+ xmasCount += 1
+ }
+ }
+ }
+
+ return xmasCount
+}
diff --git a/src/2024/2024-12-04/lib/xmasCount.ts b/src/2024/2024-12-04/lib/xmasCount.ts
new file mode 100644
index 0000000..db52f07
--- /dev/null
+++ b/src/2024/2024-12-04/lib/xmasCount.ts
@@ -0,0 +1,56 @@
+import { checkDiagonal } from './wordCheckerUtils.js'
+
+/**
+ * Counts the number of (2x) crossed "MAS" words overlapping at the letter "A"
+ * @param data {CharacterArray} 2D string array containing the input text
+ * @param WORD_TO_FIND {string} Word to find ("MAS")
+ * @returns {number} Number of overlapping crossed-over ("MAS") `WORD`s
+ */
+export const countMASword = (data: string[][], WORD_TO_FIND: string = 'MAS'): number => {
+ const wordLength = WORD_TO_FIND.length
+ const middleZeroBasedIndex = (Math.floor(wordLength / 2) + wordLength % 2) - 1
+ const offset = middleZeroBasedIndex
+ const reverseWord = WORD_TO_FIND.split('').reverse().join('')
+
+ let count = 0
+
+ for (let y = 0; y < data.length; y += 1) {
+ const row = data[y]
+
+ if (row === undefined) continue
+
+ for (let x = 0; x < row.length; x += 1) {
+ const letter = row[x]
+
+ if (letter !== WORD_TO_FIND[middleZeroBasedIndex]) continue
+
+ if (
+ (checkDiagonal(
+ { x: x + offset, y: y - offset },
+ { xDirection: -1, yDirection: 1, data },
+ WORD_TO_FIND
+ ) ||
+ checkDiagonal(
+ { x: x + offset, y: y - offset },
+ { xDirection: -1, yDirection: 1, data },
+ reverseWord)
+ ) &&
+
+ (checkDiagonal(
+ { x: x + offset, y: y + offset },
+ { xDirection: -1, yDirection: -1, data },
+ WORD_TO_FIND
+ ) ||
+ checkDiagonal(
+ { x: x + offset, y: y + offset },
+ { xDirection: -1, yDirection: -1, data },
+ reverseWord)
+ )
+ ) {
+ count += 1
+ }
+ }
+ }
+
+ return count
+}
diff --git a/src/2024/2024-12-04/main.ts b/src/2024/2024-12-04/main.ts
new file mode 100644
index 0000000..5fefdc7
--- /dev/null
+++ b/src/2024/2024-12-04/main.ts
@@ -0,0 +1,23 @@
+import path from 'path'
+import { currentDirectory, readFile } from '@/utils/file.js'
+import { wordCount } from './lib/wordCount.js'
+import { countMASword } from './lib/xmasCount.js'
+
+const directory = currentDirectory(import.meta.url)
+
+const data: string[][] = readFile(path.join(directory, 'input.txt'))
+ .split('\n')
+ .map(row => row.split(''))
+
+const quiz20241204_01 = () => {
+ const count = wordCount(data, 'XMAS')
+ console.log('"XMAS" word count:', count)
+}
+
+const quiz20241204_02 = () => {
+ const count = countMASword(data, 'MAS')
+ console.log('"MAS-only" word count:', count)
+}
+
+quiz20241204_01()
+quiz20241204_02()
diff --git a/src/2024/2024-12-04/sample.test.ts b/src/2024/2024-12-04/sample.test.ts
new file mode 100644
index 0000000..e2b4fdd
--- /dev/null
+++ b/src/2024/2024-12-04/sample.test.ts
@@ -0,0 +1,19 @@
+import { test, expect } from 'vitest'
+import { wordCount } from './lib/wordCount.js'
+import { countMASword } from './lib/xmasCount.js'
+
+const data: string[][] = `ABCDEFGHIJ
+XMASXMASXM
+GMUMSHLGWH
+MXAXAXMHRD
+ASHSGJM2AX`
+ .split('\n')
+ .map(row => row.split(''))
+
+test('Count "XMAS" words - demo', () => {
+ expect(wordCount(data, 'XMAS')).toBe(4)
+})
+
+test('Count "MAS-only" crossed words - demo', () => {
+ expect(countMASword(data, 'MAS')).toBe(1)
+})