Skip to content

Commit 84083fe

Browse files
committed
Add Day 5
1 parent 8a2ad25 commit 84083fe

File tree

5 files changed

+1688
-1
lines changed

5 files changed

+1688
-1
lines changed

day-5/README.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Day 5: Print Queue
2+
3+
<br>
4+
5+
## Part 1
6+
7+
Satisfied with their search on Ceres, the squadron of scholars suggests subsequently scanning the stationery stacks of sub-basement 17.
8+
9+
The North Pole printing department is busier than ever this close to Christmas, and while The Historians continue their search of this
10+
historically significant facility, an Elf operating a [very familiar printer](https://adventofcode.com/2017/day/1) beckons you over.
11+
12+
The Elf must recognize you, because they waste no time explaining that the new **sleigh launch safety manual** updates won't print
13+
correctly. Failure to update the safety manuals would be dire indeed, so you offer your services.
14+
15+
Safety protocols clearly indicate that new pages for the safety manuals must be printed in a **very specific order**. The notation `X|Y`
16+
means that if both page number `X` and page number `Y` are to be produced as part of an update, page number `X` **must** be printed at some
17+
point before page number `Y`.
18+
19+
The Elf has for you both the **page ordering rules** and the **pages to produce in each update** (your puzzle input), but can't figure out
20+
whether each update has the pages in the right order.
21+
22+
For example:
23+
24+
```txt
25+
47|53
26+
97|13
27+
97|61
28+
97|47
29+
75|29
30+
61|13
31+
75|53
32+
29|13
33+
97|29
34+
53|29
35+
61|53
36+
97|53
37+
61|29
38+
47|13
39+
75|47
40+
97|75
41+
47|61
42+
75|61
43+
47|29
44+
75|13
45+
53|13
46+
47+
75,47,61,53,29
48+
97,61,53,29,13
49+
75,29,13
50+
75,97,47,61,53
51+
61,13,29
52+
97,13,75,29,47
53+
```
54+
55+
The first section specifies the **page ordering rules**, one per line. The first rule, `47|53`, means that if an update includes both page
56+
number 47 and page number 53, then page number 47 **must** be printed at some point before page number 53. (47 doesn't necessarily need to
57+
be **immediately** before 53; other pages are allowed to be between them.)
58+
59+
The second section specifies the page numbers of each **update**. Because most safety manuals are different, the pages needed in the updates
60+
are different too. The first update, `75,47,61,53,29`, means that the update consists of page numbers 75, 47, 61, 53, and 29.
61+
62+
To get the printers going as soon as possible, start by identifying **which updates are already in the right order**.
63+
64+
In the above example, the first update (75,47,61,53,29) is in the right order:
65+
66+
- `75` is correctly first because there are rules that put each other page after it: `75|47`, `75|61`, `75|53`, and `75|29`.
67+
- `47` is correctly second because 75 must be before it (`75|47`) and every other page must be after it according to `47|61`, `47|53`, and
68+
`47|29`.
69+
- `61` is correctly in the middle because 75 and 47 are before it (`75|61` and `47|61`) and 53 and 29 are after it (`61|53` and `61|29`).
70+
- `53` is correctly fourth because it is before page number 29 (`53|29`).
71+
- `29` is the only page left and so is correctly last.
72+
73+
Because the first update does not include some page numbers, the ordering rules involving those missing page numbers are ignored.
74+
75+
The second and third updates are also in the correct order according to the rules. Like the first update, they also do not include every
76+
page number, and so only some of the ordering rules apply - within each update, the ordering rules that involve missing page numbers are not
77+
used.
78+
79+
The fourth update, `75,97,47,61,53`, is **not** in the correct order: it would print 75 before 97, which violates the rule `97|75`.
80+
81+
The fifth update, `61,13,29`, is also **not** in the correct order, since it breaks the rule `29|13`.
82+
83+
The last update, `97,13,75,29,47`, is **not** in the correct order due to breaking several rules.
84+
85+
For some reason, the Elves also need to know the **middle page number** of each update being printed. Because you are currently only
86+
printing the correctly-ordered updates, you will need to find the middle page number of each correctly-ordered update. In the above example,
87+
the correctly-ordered updates are:
88+
89+
```txt
90+
75,47,61,53,29
91+
97,61,53,29,13
92+
75,29,13
93+
```
94+
95+
These have middle page numbers of `61`, `53`, and `29` respectively. Adding these page numbers together gives `143`.
96+
97+
Of course, you'll need to be careful: the actual list of **page ordering rules** is bigger and more complicated than the above example.
98+
99+
Determine which updates are already in the correct order. **What do you get if you add up the middle page number from those
100+
correctly-ordered updates?**
101+
102+
<br>
103+
104+
## Part 2
105+
106+
While the Elves get to work printing the correctly-ordered updates, you have a little time to fix the rest of them.
107+
108+
For each of the **incorrectly-ordered updates**, use the page ordering rules to put the page numbers in the right order. For the above
109+
example, here are the three incorrectly-ordered updates and their correct orderings:
110+
111+
- `75,97,47,61,53` becomes `97,75,47,61,53`.
112+
- `61,13,29` becomes `61,29,13`.
113+
- `97,13,75,29,47` becomes `97,75,47,29,13`.
114+
115+
After taking **only the incorrectly-ordered updates** and ordering them correctly, their middle page numbers are `47`, `29`, and `47`.
116+
Adding these together produces `123`.
117+
118+
Find the updates which are not in the correct order. **What do you get if you add up the middle page numbers after correctly ordering just
119+
those updates?**

day-5/day-5.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { describe, it } from 'node:test';
2+
import assert from 'node:assert';
3+
import path from 'node:path';
4+
5+
import { calculateSumOfMiddlePageNumbersOfCorrectedUpdates, calculateSumOfMiddlePageNumbersOfCorrectlyOrderedUpdates } from './day-5';
6+
7+
describe('Day 5: Print Queue', () => {
8+
const updatesFilePath = path.join(__dirname, 'updates.txt');
9+
10+
it('Part 1: should calculate sum of middle page numbers of correctly ordered updates', async () => {
11+
const expectedSumOfMiddlePageNumbersOfCorrectlyOrderedUpdates = 6051; // Verified for this dataset
12+
13+
const sumOfMiddlePageNumbersOfCorrectlyOrderedUpdates = await calculateSumOfMiddlePageNumbersOfCorrectlyOrderedUpdates(updatesFilePath);
14+
15+
assert.strictEqual(sumOfMiddlePageNumbersOfCorrectlyOrderedUpdates, expectedSumOfMiddlePageNumbersOfCorrectlyOrderedUpdates);
16+
});
17+
18+
it('Part 2: should calculate sum of middle page numbers of corrected updates', async () => {
19+
const expectedSumOfMiddlePageNumbersOfCorrectedUpdates = 5093; // Verified for this dataset
20+
21+
const sumOfMiddlePageNumbersOfCorrectedUpdates = await calculateSumOfMiddlePageNumbersOfCorrectedUpdates(updatesFilePath);
22+
23+
assert.strictEqual(sumOfMiddlePageNumbersOfCorrectedUpdates, expectedSumOfMiddlePageNumbersOfCorrectedUpdates);
24+
});
25+
});

day-5/day-5.ts

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import fs from 'node:fs/promises';
2+
3+
/**
4+
* Read file
5+
*/
6+
const readFile = async (filePath: string): Promise<string> => {
7+
const fileContents = await fs.readFile(filePath, {
8+
encoding: 'utf8',
9+
});
10+
const normalizedFileContents = fileContents.trim().split(/\r?\n/).join('\n');
11+
return normalizedFileContents;
12+
};
13+
14+
/**
15+
* Parse page ordering rules
16+
*/
17+
const parsePageOrderingRules = (updatesFileContent: string): Array<[number, number]> => {
18+
return updatesFileContent
19+
.split('\n\n')[0]
20+
.split('\n')
21+
.map((pageRuleAsString) => {
22+
return pageRuleAsString.split('|').map((pageAsString) => {
23+
return parseInt(pageAsString, 10);
24+
}) as [number, number];
25+
});
26+
};
27+
28+
/**
29+
* Parse updates
30+
*/
31+
const parseUpdates = (updatesFileContent: string): Array<Array<number>> => {
32+
return updatesFileContent
33+
.split('\n\n')[1]
34+
.split('\n')
35+
.map((pageRuleAsString) => {
36+
return pageRuleAsString.split(',').map((pageAsString) => {
37+
return parseInt(pageAsString, 10);
38+
});
39+
});
40+
};
41+
42+
/**
43+
* Checks whether the given pages are correctly ordered
44+
*/
45+
const arePagesCorrectlyOrdered = (pages: Array<number>, pageOrderingRules: Array<[number, number]>): boolean => {
46+
return pages.every((page, pageIndex) => {
47+
// Find page rules
48+
const applicablePageOrderingRules = pageOrderingRules.filter((pageOrderingRule) => {
49+
return pageOrderingRule.some((pageOrderingRulePart) => {
50+
return pageOrderingRulePart === page;
51+
});
52+
});
53+
54+
// Check whether page is valid based on applicable rules
55+
const isValidPage = applicablePageOrderingRules.every((applicablePageOrderingRule) => {
56+
return applicablePageOrderingRule[0] === page
57+
? pages.slice(0, pageIndex).every((pageBeforeCurrentPage) => {
58+
return pageBeforeCurrentPage !== applicablePageOrderingRule[1];
59+
})
60+
: pages.slice(pageIndex + 1).every((pageAfterCurrentPage) => {
61+
return pageAfterCurrentPage !== applicablePageOrderingRule[0];
62+
});
63+
});
64+
65+
// Done
66+
return isValidPage;
67+
});
68+
};
69+
70+
/**
71+
* Correct order of pages
72+
*/
73+
const correctOrderOfPages = (pages: Array<number>, pageOrderingRules: Array<[number, number]>): Array<number> => {
74+
// Re-order pages (in-place mutation) separately from original pages to keep track of progress
75+
let correctlyOrderedPages = structuredClone(pages);
76+
77+
// Look at each page, and correct if necessary
78+
for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) {
79+
// Find the last page that needs to be before the current page
80+
const lastRequiredPreviousPageIndex = Math.max(
81+
...pageOrderingRules
82+
.filter((pageOrderingRule) => {
83+
return pageOrderingRule[1] === pages[pageIndex];
84+
})
85+
.map((pageOrderingRule) => {
86+
return pageOrderingRule[0];
87+
})
88+
.map((previousPage) => {
89+
return correctlyOrderedPages.indexOf(previousPage);
90+
}),
91+
);
92+
93+
// Skip if this page does not need to be re-ordered (shifted back)
94+
const pageIndexInCorrectlyOrderedPages = correctlyOrderedPages.indexOf(pages[pageIndex]);
95+
if (lastRequiredPreviousPageIndex < pageIndexInCorrectlyOrderedPages) {
96+
continue; // Continue early
97+
}
98+
99+
// Re-assemble pages by shifting the current page back behind the last required previous page (in-place mutation)
100+
correctlyOrderedPages = [
101+
...correctlyOrderedPages.slice(0, pageIndexInCorrectlyOrderedPages),
102+
...correctlyOrderedPages.slice(pageIndexInCorrectlyOrderedPages + 1, lastRequiredPreviousPageIndex),
103+
correctlyOrderedPages[lastRequiredPreviousPageIndex],
104+
correctlyOrderedPages[pageIndexInCorrectlyOrderedPages],
105+
...correctlyOrderedPages.slice(lastRequiredPreviousPageIndex + 1),
106+
];
107+
}
108+
109+
// Done
110+
return correctlyOrderedPages;
111+
};
112+
113+
/**
114+
* Calculate sum of middle page numbers
115+
*/
116+
const calculateSumOfMiddlePageNumbers = (updates: Array<Array<number>>): number => {
117+
return updates
118+
.map((pages) => {
119+
return pages[(pages.length - 1) / 2];
120+
})
121+
.reduce((sumOfMiddlePageNumbers, middlePageNumber) => {
122+
return sumOfMiddlePageNumbers + middlePageNumber;
123+
}, 0);
124+
};
125+
126+
/**
127+
* Part 1: Calculate sum of middle page numbers of correctly ordered updates
128+
*/
129+
export const calculateSumOfMiddlePageNumbersOfCorrectlyOrderedUpdates = async (updatesFilePath: string) => {
130+
// Get data
131+
const updatesFileContent = await readFile(updatesFilePath);
132+
const pageOrderingRules = parsePageOrderingRules(updatesFileContent);
133+
const updates = parseUpdates(updatesFileContent);
134+
135+
// Find correctly ordered updates
136+
const correctlyOrderedUpdates = updates.filter((pages) => {
137+
return arePagesCorrectlyOrdered(pages, pageOrderingRules);
138+
});
139+
140+
// Calculate sum of middle page numbers of correctly ordered updates
141+
const sumOfMiddlePageNumbersOfCorrectlyOrderedUpdates = calculateSumOfMiddlePageNumbers(correctlyOrderedUpdates);
142+
143+
// Done
144+
return sumOfMiddlePageNumbersOfCorrectlyOrderedUpdates;
145+
};
146+
147+
/**
148+
* Part 2: Calculate sum of middle page numbers of corrected updates
149+
*/
150+
export const calculateSumOfMiddlePageNumbersOfCorrectedUpdates = async (updatesFilePath: string) => {
151+
// Get data
152+
const updatesFileContent = await readFile(updatesFilePath);
153+
const pageOrderingRules = parsePageOrderingRules(updatesFileContent);
154+
const updates = parseUpdates(updatesFileContent);
155+
156+
// Find incorrectly ordered updates
157+
const incorrectlyOrderedUpdates = updates.filter((pages) => {
158+
return !arePagesCorrectlyOrdered(pages, pageOrderingRules);
159+
});
160+
161+
// Correct incorrectly ordered updates
162+
const correctedUpdates = incorrectlyOrderedUpdates.map((pages) => {
163+
return correctOrderOfPages(pages, pageOrderingRules);
164+
});
165+
166+
// Calculate sum of middle page numbers of correctly ordered updates
167+
const sumOfMiddlePageNumbersOfCorrectedUpdates = calculateSumOfMiddlePageNumbers(correctedUpdates);
168+
169+
// Done
170+
return sumOfMiddlePageNumbersOfCorrectedUpdates;
171+
};

0 commit comments

Comments
 (0)