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 +}