Skip to content

Commit 5bec5e7

Browse files
committed
Add Day 2
1 parent 94acbef commit 5bec5e7

File tree

5 files changed

+1212
-1
lines changed

5 files changed

+1212
-1
lines changed

day-2/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Day 2: Red-Nosed Reports
2+
3+
<br>
4+
5+
## Part 1
6+
7+
Fortunately, the first location The Historians want to search isn't a long walk from the Chief Historian's office.
8+
9+
While the [Red-Nosed Reindeer nuclear fusion/fission plant](https://adventofcode.com/2015/day/19) appears to contain no sign of the Chief
10+
Historian, the engineers there run up to you as soon as they see you. Apparently, they **still** talk about the time Rudolph was saved
11+
through molecular synthesis from a single electron.
12+
13+
They're quick to add that - since you're already here - they'd really appreciate your help analyzing some unusual data from the Red-Nosed
14+
reactor. You turn to check if The Historians are waiting for you, but they seem to have already divided into groups that are currently
15+
searching every corner of the facility. You offer to help with the unusual data.
16+
17+
The unusual data (your puzzle input) consists of many **reports**, one report per line. Each report is a list of numbers called **levels**
18+
that are separated by spaces. For example:
19+
20+
```txt
21+
7 6 4 2 1
22+
1 2 7 8 9
23+
9 7 6 2 1
24+
1 3 2 4 5
25+
8 6 4 4 1
26+
1 3 6 7 9
27+
```
28+
29+
This example data contains six reports each containing five levels.
30+
31+
The engineers are trying to figure out which reports are **safe**. The Red-Nosed reactor safety systems can only tolerate levels that are
32+
either gradually increasing or gradually decreasing. So, a report only counts as safe if both of the following are true:
33+
34+
- The levels are either **all increasing** or **all decreasing**.
35+
- Any two adjacent levels differ by **at least one** and **at most three**.
36+
37+
In the example above, the reports can be found safe or unsafe by checking those rules:
38+
39+
- `7 6 4 2 1`: **Safe** because the levels are all decreasing by 1 or 2.
40+
- `1 2 7 8 9`: **Unsafe** because `2 7` is an increase of 5.
41+
- `9 7 6 2 1`: **Unsafe** because `6 2` is a decrease of 4.
42+
- `1 3 2 4 5`: **Unsafe** because `1 3` is increasing but `3 2` is decreasing.
43+
- `8 6 4 4 1`: **Unsafe** because `4 4` is neither an increase or a decrease.
44+
- `1 3 6 7 9`: **Safe** because the levels are all increasing by 1, 2, or 3.
45+
46+
So, in this example, `2` reports are **safe**.
47+
48+
Analyze the unusual data from the engineers. **How many reports are safe?**
49+
50+
<br>
51+
52+
## Part 2
53+
54+
The engineers are surprised by the low number of safe reports until they realize they forgot to tell you about the Problem Dampener.
55+
56+
The Problem Dampener is a reactor-mounted module that lets the reactor safety systems **tolerate a single bad level** in what would
57+
otherwise be a safe report. It's like the bad level never happened!
58+
59+
Now, the same rules apply as before, except if removing a single level from an unsafe report would make it safe, the report instead counts
60+
as safe.
61+
62+
More of the above example's reports are now safe:
63+
64+
- `7 6 4 2 1`: **Safe** without removing any level.
65+
- `1 2 7 8 9`: **Unsafe** regardless of which level is removed.
66+
- `9 7 6 2 1`: **Unsafe** regardless of which level is removed.
67+
- `1 3 2 4 5`: **Safe** by removing the second level, `3`.
68+
- `8 6 4 4 1`: **Safe** by removing the third level, `4`.
69+
- `1 3 6 7 9`: **Safe** without removing any level.
70+
71+
Thanks to the Problem Dampener, `4` reports are actually **safe**!
72+
73+
Update your analysis by handling situations where the Problem Dampener can remove a single level from unsafe reports. **How many reports are
74+
now safe?**

day-2/day-2.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 { determineNumberOfSafeReports, determineNumberOfSafeReportsWithProblemDampener } from './day-2';
6+
7+
describe('Day 2: Red-Nosed Reports', () => {
8+
const reportsFilePath = path.join(__dirname, 'reports.txt');
9+
10+
it('Part 1: should determine number of safe reports', async () => {
11+
const expectedNumberOfSafeReports = 660; // Verified for this dataset
12+
13+
const numberOfSafeReports = await determineNumberOfSafeReports(reportsFilePath);
14+
15+
assert.strictEqual(numberOfSafeReports, expectedNumberOfSafeReports);
16+
});
17+
18+
it('Part 2: should determine number of safe reports with problem dampener', async () => {
19+
const expectedNumberOfSafeReportsWithProblemDampener = 689; // Verified for this dataset
20+
21+
const numberOfSafeReports = await determineNumberOfSafeReportsWithProblemDampener(reportsFilePath);
22+
23+
assert.strictEqual(numberOfSafeReports, expectedNumberOfSafeReportsWithProblemDampener);
24+
});
25+
});

day-2/day-2.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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 reports
16+
*/
17+
const parseReports = (reportsFileContent: string) => {
18+
return reportsFileContent.split('\n').map((levelsAsString) => {
19+
return levelsAsString.split(' ').map((levelAsString) => {
20+
return parseInt(levelAsString, 10);
21+
});
22+
});
23+
};
24+
25+
/**
26+
* Check whether the given levels are safe
27+
*/
28+
const areLevelsSafe = (levels: Array<number>) => {
29+
// Calculate difference between each adjacent level
30+
const levelDifferences: Array<number> = [];
31+
for (let levelIndex = 0; levelIndex < levels.length - 1; levelIndex++) {
32+
levelDifferences.push(levels[levelIndex] - levels[levelIndex + 1]);
33+
}
34+
35+
// Check whether all level differences are within the safe range
36+
const safeLevelRange = [1, 3];
37+
const areLevelDifferencesWithinSafeRange = levelDifferences.every((levelDifference) => {
38+
return Math.abs(levelDifference) >= safeLevelRange[0] && Math.abs(levelDifference) <= safeLevelRange[1];
39+
});
40+
if (!areLevelDifferencesWithinSafeRange) {
41+
return false; // Early exit
42+
}
43+
44+
// Check whether all level differences continue in one direction (increase or decrease)
45+
const hasAnyPositiveLevelDifferences = levelDifferences.some((levelDiff) => {
46+
return levelDiff > 0;
47+
});
48+
const hasAnyNegativeLevelDifferences = levelDifferences.some((levelDiff) => {
49+
return levelDiff < 0;
50+
});
51+
const doAllLevelsContinueInOneDirection =
52+
(hasAnyPositiveLevelDifferences && !hasAnyNegativeLevelDifferences) ||
53+
(hasAnyNegativeLevelDifferences && !hasAnyPositiveLevelDifferences);
54+
if (!doAllLevelsContinueInOneDirection) {
55+
return false; // Early exit
56+
}
57+
58+
// All good :)
59+
return true;
60+
};
61+
62+
/**
63+
* Part 1: Determine number of safe reports
64+
*/
65+
export const determineNumberOfSafeReports = async (reportsFilePath: string) => {
66+
// Get data
67+
const reportsFileContent = await readFile(reportsFilePath);
68+
const reports = parseReports(reportsFileContent);
69+
70+
// Determine number of safe reports
71+
const numberOfSafeReports = reports.filter((levels) => {
72+
return areLevelsSafe(levels);
73+
}).length;
74+
75+
// Done
76+
return numberOfSafeReports;
77+
};
78+
79+
/**
80+
* Part 2: Determine number of safe reports with problem dampener
81+
*/
82+
export const determineNumberOfSafeReportsWithProblemDampener = async (reportsFilePath: string) => {
83+
// Get data
84+
const reportsFileContent = await readFile(reportsFilePath);
85+
const reports = parseReports(reportsFileContent);
86+
87+
// Determine number of safe reports with problem dampener
88+
const numberOfSafeReportsWithProblemDampener = reports.filter((levels) => {
89+
// Construct levels in all variants (#bruteforce)
90+
const levelVariants = [levels];
91+
for (let levelIndex = 0; levelIndex < levels.length; levelIndex++) {
92+
levelVariants.push([...levels.slice(0, levelIndex), ...levels.slice(levelIndex + 1)]);
93+
}
94+
95+
// Check every variant
96+
let isAnyLevelVariantSafe = false;
97+
for (let levelVariantIndex = 0; levelVariantIndex < levelVariants.length; levelVariantIndex++) {
98+
const isLevelVariantSafe = areLevelsSafe(levelVariants[levelVariantIndex]);
99+
if (isLevelVariantSafe) {
100+
isAnyLevelVariantSafe = true;
101+
break; // Early exit
102+
}
103+
}
104+
105+
// Done
106+
return isAnyLevelVariantSafe;
107+
}).length;
108+
109+
// Done
110+
return numberOfSafeReportsWithProblemDampener;
111+
};

0 commit comments

Comments
 (0)