diff --git a/README.md b/README.md
index 5a874a0..a92f47c 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ This repository contains solutions and a local development environment for the [
- Day 2: Red-Nosed Reports [[link]](/src/2024/2024-12-02/README.md)
- 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)
diff --git a/src/2024/2024-12-05/README.md b/src/2024/2024-12-05/README.md
new file mode 100644
index 0000000..3061ee8
--- /dev/null
+++ b/src/2024/2024-12-05/README.md
@@ -0,0 +1,14 @@
+## Day 5: Print Queue
+
+Visit the Advent of Code website for more information on this puzzle at:
+
+**Source:** https://adventofcode.com/2024/day/5
+**Status:** Complete ⭐⭐
+
+
+
+| Code | Description |
+| --- | --- |
+| **orderedUpdates.ts** | Has functions for finding correct "updates" according to defined "rules." It also counts the sum of middle page numbers from correctly-ordered "updates." |
+| **fixOrderingUpdates.ts** | Has functions for correcting incorrectly placed "updates" before calculating the sum of the corrected middle page numbers. |
+| **fileReader.ts** | Reads and transforms a text file for processing by the quiz scripts. |
diff --git a/src/2024/2024-12-05/input.txt b/src/2024/2024-12-05/input.txt
new file mode 100644
index 0000000..811ff9c
--- /dev/null
+++ b/src/2024/2024-12-05/input.txt
@@ -0,0 +1,28 @@
+39|89
+101|15
+101|95
+101|39
+5|48
+95|15
+5|89
+48|15
+101|48
+89|48
+95|89
+101|89
+95|48
+39|15
+5|39
+101|5
+39|95
+5|95
+39|48
+5|15
+89|15
+
+5,39,95,89,48
+101,95,89,48,15
+5,48,15
+5,101,39,95,89
+95,15,48
+101,15,5,48,39
\ No newline at end of file
diff --git a/src/2024/2024-12-05/lib/fileReader.ts b/src/2024/2024-12-05/lib/fileReader.ts
new file mode 100644
index 0000000..3ab063f
--- /dev/null
+++ b/src/2024/2024-12-05/lib/fileReader.ts
@@ -0,0 +1,56 @@
+import path from 'path'
+import { currentDirectory, readFile } from '@/utils/file.js'
+import { uniformArrayElements } from '@/utils/arrays.js'
+
+export type Rules = Record
+
+export type QuizData = {
+ rules: Rules;
+ updates: number[][]
+}
+
+/**
+ * Reads and transforms quiz input text for processing.
+ * @param fileName {string} Filename of input text relative to the calling function
+ * @returns {QuizData} Formatted data
+ */
+export const fileReader = (fileName: string): QuizData => {
+ const directory = currentDirectory(import.meta.url)
+ const file = readFile(path.join(directory, '..', fileName))
+
+ const segments = file.split('\n\n')
+
+ if (
+ segments[0] === undefined || // rules
+ segments[1] === undefined // updates
+ ) {
+ throw new Error('Undefined rules or updates data')
+ }
+
+ const rules: Rules = segments[0]
+ .split('\n')
+ .reduce((list: Rules, item) => {
+ const pair: number[] = item.split('|').map(Number)
+
+ if (!uniformArrayElements(pair, 'number')) {
+ throw new Error('Invalid rules value')
+ }
+
+ const key = pair[0] as number
+
+ if (list[key] === undefined) {
+ list[key] = [pair[1] as number]
+ } else {
+ list[key].push(pair[1] as number)
+ }
+
+ return list
+ }, {})
+
+ return {
+ rules,
+ updates: segments[1]
+ .split('\n')
+ .map(item => item.split(',').map(Number))
+ }
+}
diff --git a/src/2024/2024-12-05/lib/fixOrderingUpdates.ts b/src/2024/2024-12-05/lib/fixOrderingUpdates.ts
new file mode 100644
index 0000000..98a6550
--- /dev/null
+++ b/src/2024/2024-12-05/lib/fixOrderingUpdates.ts
@@ -0,0 +1,116 @@
+import { isOrderedReport } from './orderedUpdates.js'
+import { arrayMiddleIndex, uniformArrayElements } from '@/utils/arrays.js'
+
+import type { Rules } from './fileReader.js'
+import type { QuizData } from './fileReader.js'
+
+type CurrentItem = {
+ value: number;
+ index: number;
+}
+
+/**
+ * Finds the next appropriate array index to swap places with the incorrectly placed current item accoring 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
+ * @returns {number} Array index to swap positions with the current item
+ */
+const nextHotSwapIndex = (rules: Rules, restItems: number[], currentItem: CurrentItem): number => {
+ let indexToSwap = -1
+
+ for (let i = currentItem.index; i < restItems.length; i += 1) {
+ if (rules[restItems[i] as number]?.includes(currentItem.value)) {
+ indexToSwap = i
+ break
+ }
+ }
+
+ if (indexToSwap === -1) {
+ throw new Error('No array item to swap with')
+ }
+
+ return indexToSwap
+}
+
+/**
+ * Fixes the ordering of an incorrectly-ordered "update" (row) by swapping its elements with target items.
+ * @param rules {Rules} Object containing parsed and formatted rules and updates data
+ * @param unorderedItems {number[]} "updates" array items content
+ * @returns {number[]} Corrected "update" items
+ */
+export const fixOrdering = (rules: Rules, unorderedItems: number[]): number[] => {
+ if (!uniformArrayElements(unorderedItems, '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 }
+
+ // Swaps incorrectly placed items with target items in the array
+ const swapItems = () => {
+ const indexToSwap = nextHotSwapIndex(
+ rules,
+ unorderedItems,
+ currentItemData
+ )
+
+ const temp = unorderedItems[indexToSwap] as number
+ unorderedItems[indexToSwap] = currentItem
+ unorderedItems[i] = temp
+ currentItem = temp
+
+ fixOrdering(rules, unorderedItems)
+ }
+
+ // Correct "update" item should have en entry in the "rules" object
+ // Swap places with other items if its incorrect
+ if (rules[currentItem] === undefined) {
+ swapItems()
+ }
+
+ // Get the rest of items after the current item for comparison
+ const afterItems = unorderedItems.filter((_, index) => index > i)
+
+ // 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()
+ }
+ }
+
+ return [...unorderedItems]
+}
+
+/**
+ * Fixes incorrectly-ordered "updates" and calculates their middle page numbers after fixing.
+ * @param data {QuizData} Object containing "rules" and "updates" input
+ * @param verbose {boolean} Flag to display processing log messages. Defaults to false.
+ * @returns {number} Total middle page numbers from corrected "updates"
+ */
+export const fixOrderingUpdates = (data: QuizData, verbose: boolean = false): number => {
+ let sum = 0
+
+ for (let i = 0; i < data.updates.length; i += 1) {
+ const isOrdered = isOrderedReport(data.rules, data.updates[i] ?? [])
+
+ if (!isOrdered) {
+ const corrected = fixOrdering(
+ data.rules,
+ [...data.updates[i] as number[]]
+ )
+
+ if (verbose) {
+ console.log('---incorrect', data.updates[i])
+ console.log('---corrected', corrected)
+ }
+
+ const middleIndex = arrayMiddleIndex(corrected)
+ const item = corrected[middleIndex - 1]
+ sum += item ?? 0
+ }
+ }
+
+ return sum
+}
diff --git a/src/2024/2024-12-05/lib/orderedUpdates.ts b/src/2024/2024-12-05/lib/orderedUpdates.ts
new file mode 100644
index 0000000..9511495
--- /dev/null
+++ b/src/2024/2024-12-05/lib/orderedUpdates.ts
@@ -0,0 +1,63 @@
+import type { QuizData, Rules } from './fileReader.js'
+import { uniformArrayElements, arrayMiddleIndex } from '@/utils/arrays.js'
+
+/**
+ * Checks if an "update" list is correct according to defined "rules"
+ * @param rules {Rules} Object containing parsed and formatted rules and updates data
+ * @param updateItems {number[]} "updates" array items content
+ * @returns Flag indicating if the set of `updateItems` is correct
+ */
+export const isOrderedReport = (rules: Rules, updateItems: number[]): boolean => {
+ let isOrdered = true
+
+ if (!uniformArrayElements(updateItems, 'number')) {
+ throw new Error('Invalid updateItems item/s')
+ }
+
+ for (let i = 0; i < updateItems.length - 1; i += 1) {
+ const currentItem = updateItems[i] as number
+
+ // Current "update" item should have en entry in the "rules" object
+ if (rules[currentItem] === undefined) {
+ isOrdered = false
+ break
+ }
+
+ // Get the rest of items after the current item for comparison
+ const afterItems = updateItems.filter((_, index) => index > i)
+
+ // Current item's "rule" should have the after-item entries
+ if (!afterItems.every(item => rules[currentItem]?.includes(item))) {
+ isOrdered = false
+ break
+ }
+ }
+
+ return isOrdered
+}
+
+/**
+ * Counts the sum of middle page numbers from correctly-ordered "updates"
+ * @param data {QuizData} Object containing "rules" and "updates" input
+ * @param verbose {boolean} Flag to display processing log messages. Defaults to false.
+ * @returns {number} Total middle page numbers from correct "updates"
+ */
+export const sumOfCorrectUpdates = (data: QuizData, verbose: boolean = false): number => {
+ let sum = 0
+
+ for (let i = 0; i < data.updates.length; i += 1) {
+ const isOrdered = isOrderedReport(data.rules, data.updates[i] ?? [])
+
+ if (isOrdered) {
+ if (verbose) {
+ console.log('---correct', data.updates[i])
+ }
+
+ const mid = arrayMiddleIndex(data.updates[i] as number[])
+ const item = data.updates[i]?.[mid - 1]
+ sum += item ?? 0
+ }
+ }
+
+ return sum
+}
diff --git a/src/2024/2024-12-05/main.ts b/src/2024/2024-12-05/main.ts
new file mode 100644
index 0000000..059baee
--- /dev/null
+++ b/src/2024/2024-12-05/main.ts
@@ -0,0 +1,29 @@
+import { fileReader } from './lib/fileReader.js'
+import type { QuizData } from './lib/fileReader.js'
+
+import { sumOfCorrectUpdates } from './lib/orderedUpdates.js'
+import { fixOrderingUpdates } from './lib/fixOrderingUpdates.js'
+
+const data: QuizData = fileReader('input.txt')
+
+/**
+ * Part 1/2 of the 2024-12-05 quiz
+ * Counts the sum of middle pages from correct updates
+ */
+const quiz20241205_01 = () => {
+ const sum = sumOfCorrectUpdates(data, true)
+ console.log('Sum of middle page numbers:', sum)
+}
+
+/**
+ * Part 2/2 of the 2024-12-05 quiz
+ * Fixes incorrectly-ordered updates and counts
+ * the sum of middle pages from corrected updates
+ */
+const quiz20241205_02 = () => {
+ const sum = fixOrderingUpdates(data, true)
+ console.log('Sum of corrected middle page numbers:', sum)
+}
+
+quiz20241205_01()
+quiz20241205_02()
diff --git a/src/2024/2024-12-05/sample.test.ts b/src/2024/2024-12-05/sample.test.ts
new file mode 100644
index 0000000..fe253c2
--- /dev/null
+++ b/src/2024/2024-12-05/sample.test.ts
@@ -0,0 +1,16 @@
+import { test, expect } from 'vitest'
+import { fileReader } from './lib/fileReader.js'
+import type { QuizData } from './lib/fileReader.js'
+
+import { sumOfCorrectUpdates } from './lib/orderedUpdates.js'
+import { fixOrderingUpdates } from './lib/fixOrderingUpdates.js'
+
+const data: QuizData = fileReader('input.txt')
+
+test('Count middle pages - demo', () => {
+ expect(sumOfCorrectUpdates(data)).toBe(232)
+})
+
+test('Fix ordering - demo', () => {
+ expect(fixOrderingUpdates(data)).toBe(126)
+})
diff --git a/src/index.ts b/src/index.ts
index 3b0c3ca..f57d7e2 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,3 +4,15 @@ export { similarityScore } from './2024/2024-12-01/lib/similarityScore.js'
export { countSafeReports } from './2024/2024-12-02/lib/countSafeReports.js'
export { extractMultiply } from './2024/2024-12-03/lib/extractMultiply.js'
export { extractMultiplyCondition } from './2024/2024-12-03/lib/extractMultiply.js'
+export { wordCount } from './2024/2024-12-04/lib/wordCount.js'
+
+export {
+ checkDiagonal,
+ checkHorizontal,
+ checkVertical
+} from './2024/2024-12-04/lib/wordCheckerUtils.js'
+
+export { isOrderedReport } from './2024/2024-12-05/lib/orderedUpdates.js'
+export { sumOfCorrectUpdates } from './2024/2024-12-05/lib/orderedUpdates.js'
+export { fixOrderingUpdates } from './2024/2024-12-05/lib/fixOrderingUpdates.js'
+export { fixOrdering } from './2024/2024-12-05/lib/fixOrderingUpdates.js'
diff --git a/src/utils/arrays.ts b/src/utils/arrays.ts
index c9ed0e6..5748cd4 100644
--- a/src/utils/arrays.ts
+++ b/src/utils/arrays.ts
@@ -18,3 +18,24 @@ export const arrangeArray = (order: ARRAY_ORDERING) =>
throw new Error('Invalid ordering')
}
}
+
+/**
+ * Checks if array elements have the same type 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
+ * @returns {boolean} Flag indicating if all array elements have the same type
+ */
+export const uniformArrayElements = (items: S[], type: T): boolean => {
+ return (
+ items.filter(value => typeof value === type).length === items.length
+ )
+}
+
+/**
+ * Retrieves the middle (center) index of an array
+ * @param list {T[]} 1-dimensional array
+ * @returns {number} Middle index of an array
+ */
+export const arrayMiddleIndex = (list: T[]): number => {
+ return Math.floor(list.length / 2) + list.length % 2
+}