From 32231b8bdce3395f5c5bb49a729b1a790856f65f Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 15 Dec 2024 12:07:30 +0800 Subject: [PATCH 1/4] feat: day 5 print queue quiz 1/2 soln, #10 --- src/2024/2024-12-05/README.md | 13 ++++ src/2024/2024-12-05/input.txt | 28 ++++++++ src/2024/2024-12-05/lib/fileReader.ts | 56 ++++++++++++++++ src/2024/2024-12-05/lib/orderedUpdates.ts | 80 +++++++++++++++++++++++ src/2024/2024-12-05/main.ts | 16 +++++ src/2024/2024-12-05/sample.test.ts | 10 +++ src/utils/arrays.ts | 21 ++++++ 7 files changed, 224 insertions(+) create mode 100644 src/2024/2024-12-05/README.md create mode 100644 src/2024/2024-12-05/input.txt create mode 100644 src/2024/2024-12-05/lib/fileReader.ts create mode 100644 src/2024/2024-12-05/lib/orderedUpdates.ts create mode 100644 src/2024/2024-12-05/main.ts create mode 100644 src/2024/2024-12-05/sample.test.ts diff --git a/src/2024/2024-12-05/README.md b/src/2024/2024-12-05/README.md new file mode 100644 index 0000000..f2a39e0 --- /dev/null +++ b/src/2024/2024-12-05/README.md @@ -0,0 +1,13 @@ +## 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:** On-going + +
+ +| 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." | +| **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/orderedUpdates.ts b/src/2024/2024-12-05/lib/orderedUpdates.ts new file mode 100644 index 0000000..2dab73e --- /dev/null +++ b/src/2024/2024-12-05/lib/orderedUpdates.ts @@ -0,0 +1,80 @@ +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" item 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 + } + + // Backtrack current item's inclusion in previous item maps + if (i > 0) { + for (let j = 0; j < i; j += 1) { + const beforeItem = updateItems[j] as number + + if (rules[beforeItem] === undefined) { + isOrdered = false + break + } + + if (!rules[beforeItem].includes(currentItem)) { + 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} + */ +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..6a08a28 --- /dev/null +++ b/src/2024/2024-12-05/main.ts @@ -0,0 +1,16 @@ +import { fileReader } from './lib/fileReader.js' +import { sumOfCorrectUpdates } from './lib/orderedUpdates.js' +import type { QuizData } from './lib/fileReader.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 = () => { + const sum = sumOfCorrectUpdates(data, true) + console.log('Sum of middle page numbers:', sum) +} + +quiz20241205() 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..ca6dcc5 --- /dev/null +++ b/src/2024/2024-12-05/sample.test.ts @@ -0,0 +1,10 @@ +import { test, expect } from 'vitest' +import { sumOfCorrectUpdates } from './lib/orderedUpdates.js' +import { fileReader } from './lib/fileReader.js' +import type { QuizData } from './lib/fileReader.js' + +const data: QuizData = fileReader('input.txt') + +test('Count middle pages - demo', () => { + expect(sumOfCorrectUpdates(data)).toBe(232) +}) 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 +} From 9f8cd6c19301eb2932ed856954c55007ffa51cad Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 15 Dec 2024 15:54:10 +0800 Subject: [PATCH 2/4] chore: remove redundant code --- src/2024/2024-12-05/lib/orderedUpdates.ts | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/2024/2024-12-05/lib/orderedUpdates.ts b/src/2024/2024-12-05/lib/orderedUpdates.ts index 2dab73e..cbf2aba 100644 --- a/src/2024/2024-12-05/lib/orderedUpdates.ts +++ b/src/2024/2024-12-05/lib/orderedUpdates.ts @@ -4,7 +4,7 @@ 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" item content + * @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 => { @@ -31,23 +31,6 @@ export const isOrderedReport = (rules: Rules, updateItems: number[]): boolean => isOrdered = false break } - - // Backtrack current item's inclusion in previous item maps - if (i > 0) { - for (let j = 0; j < i; j += 1) { - const beforeItem = updateItems[j] as number - - if (rules[beforeItem] === undefined) { - isOrdered = false - break - } - - if (!rules[beforeItem].includes(currentItem)) { - isOrdered = false - break - } - } - } } return isOrdered From 2640e10cd677c081d3562544dee03a39fbb98b4e Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 15 Dec 2024 16:39:19 +0800 Subject: [PATCH 3/4] feat: day 5 print queue quiz 2/2 soln, #10 --- src/2024/2024-12-05/README.md | 3 +- src/2024/2024-12-05/lib/fixOrderingUpdates.ts | 116 ++++++++++++++++++ src/2024/2024-12-05/lib/orderedUpdates.ts | 2 +- src/2024/2024-12-05/main.ts | 19 ++- src/2024/2024-12-05/sample.test.ts | 8 +- 5 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 src/2024/2024-12-05/lib/fixOrderingUpdates.ts diff --git a/src/2024/2024-12-05/README.md b/src/2024/2024-12-05/README.md index f2a39e0..3061ee8 100644 --- a/src/2024/2024-12-05/README.md +++ b/src/2024/2024-12-05/README.md @@ -3,11 +3,12 @@ Visit the Advent of Code website for more information on this puzzle at: **Source:** https://adventofcode.com/2024/day/5
-**Status:** On-going +**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/lib/fixOrderingUpdates.ts b/src/2024/2024-12-05/lib/fixOrderingUpdates.ts new file mode 100644 index 0000000..6dee9af --- /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 + */ +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 index cbf2aba..9511495 100644 --- a/src/2024/2024-12-05/lib/orderedUpdates.ts +++ b/src/2024/2024-12-05/lib/orderedUpdates.ts @@ -40,7 +40,7 @@ export const isOrderedReport = (rules: Rules, updateItems: number[]): boolean => * 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} + * @returns {number} Total middle page numbers from correct "updates" */ export const sumOfCorrectUpdates = (data: QuizData, verbose: boolean = false): number => { let sum = 0 diff --git a/src/2024/2024-12-05/main.ts b/src/2024/2024-12-05/main.ts index 6a08a28..059baee 100644 --- a/src/2024/2024-12-05/main.ts +++ b/src/2024/2024-12-05/main.ts @@ -1,16 +1,29 @@ import { fileReader } from './lib/fileReader.js' -import { sumOfCorrectUpdates } from './lib/orderedUpdates.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 = () => { +const quiz20241205_01 = () => { const sum = sumOfCorrectUpdates(data, true) console.log('Sum of middle page numbers:', sum) } -quiz20241205() +/** + * 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 index ca6dcc5..fe253c2 100644 --- a/src/2024/2024-12-05/sample.test.ts +++ b/src/2024/2024-12-05/sample.test.ts @@ -1,10 +1,16 @@ import { test, expect } from 'vitest' -import { sumOfCorrectUpdates } from './lib/orderedUpdates.js' 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) +}) From bd1b193ccacc689ebc41861be84e544b7200c69a Mon Sep 17 00:00:00 2001 From: weaponsforge Date: Sun, 15 Dec 2024 16:47:17 +0800 Subject: [PATCH 4/4] chore: include newer fxns to export --- README.md | 1 + src/2024/2024-12-05/lib/fixOrderingUpdates.ts | 2 +- src/index.ts | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) 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/lib/fixOrderingUpdates.ts b/src/2024/2024-12-05/lib/fixOrderingUpdates.ts index 6dee9af..98a6550 100644 --- a/src/2024/2024-12-05/lib/fixOrderingUpdates.ts +++ b/src/2024/2024-12-05/lib/fixOrderingUpdates.ts @@ -39,7 +39,7 @@ const nextHotSwapIndex = (rules: Rules, restItems: number[], currentItem: Curren * @param unorderedItems {number[]} "updates" array items content * @returns {number[]} Corrected "update" items */ -const fixOrdering = (rules: Rules, unorderedItems: number[]): number[] => { +export const fixOrdering = (rules: Rules, unorderedItems: number[]): number[] => { if (!uniformArrayElements(unorderedItems, 'number')) { throw new Error('Invalid item/s') } 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'