Skip to content

Commit 0722e7a

Browse files
committed
feat(day05): Part 1
1 parent 1fad365 commit 0722e7a

File tree

7 files changed

+387
-75
lines changed

7 files changed

+387
-75
lines changed

day01.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const calibrationValue = (line: string): number => {
99
.split("")
1010
.reverse()
1111
.find((s) => /[0-9]/.test(s));
12-
return parseInt(`${first}${last}`);
12+
return parseInt(`${first}${last}`, 10);
1313
};
1414

1515
const numberMap = {
@@ -41,8 +41,8 @@ const findFirstNumber = (line: string, reverse = false): string | undefined =>
4141
]
4242
.filter(([, index]) => index > -1)
4343
.sort(([, index1], [, index2]) => index1 - index2)[0]?.[0] as
44-
| keyof typeof numberMap
45-
| undefined;
44+
| keyof typeof numberMap
45+
| undefined;
4646

4747
/**
4848
* Find a number at the beginning and at the end of the line.

day03_test.ts

Lines changed: 43 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -20,42 +20,29 @@ const schematics = [
2020
Deno.test("Day 3: Gear Ratios", async (t) => {
2121
await t.step("Part 1", async (t) => {
2222
await t.step("Example", async (t) => {
23-
await t.step(
24-
"findPartNumbers()",
25-
() =>
26-
assertEquals(findPartNumbers(schematics), [
27-
{ n: 467, s: [{ s: "*", col: 3, row: 1 }] },
28-
{ n: 35, s: [{ s: "*", col: 3, row: 1 }] },
29-
{ n: 633, s: [{ s: "#", col: 6, row: 3 }] },
30-
{ n: 617, s: [{ s: "*", col: 3, row: 4 }] },
31-
{ n: 592, s: [{ s: "+", col: 5, row: 5 }] },
32-
{ n: 755, s: [{ s: "*", col: 5, row: 8 }] },
33-
{ n: 664, s: [{ s: "$", col: 3, row: 8 }] },
34-
{ n: 598, s: [{ s: "*", col: 5, row: 8 }] },
35-
]),
23+
await t.step("findPartNumbers()", () =>
24+
assertEquals(findPartNumbers(schematics), [
25+
{ n: 467, s: [{ s: "*", col: 3, row: 1 }] },
26+
{ n: 35, s: [{ s: "*", col: 3, row: 1 }] },
27+
{ n: 633, s: [{ s: "#", col: 6, row: 3 }] },
28+
{ n: 617, s: [{ s: "*", col: 3, row: 4 }] },
29+
{ n: 592, s: [{ s: "+", col: 5, row: 5 }] },
30+
{ n: 755, s: [{ s: "*", col: 5, row: 8 }] },
31+
{ n: 664, s: [{ s: "$", col: 3, row: 8 }] },
32+
{ n: 598, s: [{ s: "*", col: 5, row: 8 }] },
33+
])
3634
);
3735

3836
await t.step(`Calculate the example solution`, () =>
39-
assertEquals(
40-
sum(
41-
findPartNumbers(schematics)
42-
.map(({ n }) => n),
43-
),
44-
4361,
45-
));
37+
assertEquals(sum(findPartNumbers(schematics).map(({ n }) => n)), 4361)
38+
);
4639
});
4740

4841
await t.step("Solution", async () => {
4942
const numbers = findPartNumbers(
50-
(await Deno.readTextFile("./input/day03.txt")).split("\n"),
51-
);
52-
assertEquals(
53-
sum(
54-
numbers
55-
.map(({ n }) => n),
56-
),
57-
529618,
43+
(await Deno.readTextFile("./input/day03.txt")).split("\n")
5844
);
45+
assertEquals(sum(numbers.map(({ n }) => n)), 529618);
5946
});
6047
});
6148

@@ -72,21 +59,21 @@ Deno.test("Day 3: Gear Ratios", async (t) => {
7259
await t.step("Part 2", async (t) => {
7360
await t.step(`Solve example`, () =>
7461
assertEquals(
75-
sum(
76-
findGears(schematics).map(([g1, g2]) => g1 * g2),
77-
),
78-
467835,
79-
));
62+
sum(findGears(schematics).map(([g1, g2]) => g1 * g2)),
63+
467835
64+
)
65+
);
8066

8167
await t.step("Solution", async () =>
8268
assertEquals(
8369
sum(
8470
findGears(
85-
(await Deno.readTextFile("./input/day03.txt")).split("\n"),
86-
).map(([g1, g2]) => g1 * g2),
71+
(await Deno.readTextFile("./input/day03.txt")).split("\n")
72+
).map(([g1, g2]) => g1 * g2)
8773
),
88-
77509019,
89-
));
74+
77509019
75+
)
76+
);
9077
});
9178
});
9279

@@ -96,7 +83,7 @@ Deno.test("Day 3: Gear Ratios", async (t) => {
9683
* TODO: refactor to search for symbols first and then numbers. This will make the code work better for part 2.
9784
*/
9885
const findPartNumbers = (
99-
schematics: string[],
86+
schematics: string[]
10087
): { n: number; s: Symbol[] }[] => {
10188
const numbers: { n: number; s: Symbol[] }[] = [];
10289
let currentNumber: string[] = [];
@@ -106,7 +93,7 @@ const findPartNumbers = (
10693
const addNumber = () => {
10794
if (currentNumber.length > 0) {
10895
numbers.push({
109-
n: parseInt(currentNumber.join("")),
96+
n: parseInt(currentNumber.join(""), 10),
11097
s: adjacentSymbols
11198
// Remove duplicate symbols
11299
.reduce<Symbol[]>(uniqueSymbols, []),
@@ -119,7 +106,8 @@ const findPartNumbers = (
119106
for (let row = 0; row < schematics.length; row++) {
120107
for (let col = 0; col < schematics[row].length; col++) {
121108
const c = schematics[row][col];
122-
if (/\d/.test(c)) { // Current symbol is a number
109+
if (/\d/.test(c)) {
110+
// Current symbol is a number
123111
currentNumber.push(c);
124112
const s = findAdjacentSymbols(schematics, row, col);
125113
if (s !== undefined) adjacentSymbols.push(...s);
@@ -140,7 +128,7 @@ type Symbol = { s: string; col: number; row: number };
140128
const findAdjacentSymbols = (
141129
schematics: string[],
142130
row: number,
143-
col: number,
131+
col: number
144132
): Symbol[] =>
145133
[
146134
findSymbolAt(schematics, row, col - 1), // left
@@ -159,7 +147,7 @@ const findAdjacentSymbols = (
159147
const findSymbolAt = (
160148
schematics: string[],
161149
row: number,
162-
col: number,
150+
col: number
163151
): Symbol | undefined => {
164152
if (col < 0) return undefined; // Outside of schematics
165153
if (row < 0) return undefined; // Outside of schematics
@@ -176,34 +164,31 @@ const findSymbolAt = (
176164
const findGears = (schematics: string[]) => {
177165
const parts = findPartNumbers(schematics);
178166
// From the known part numbers
179-
return parts
180-
// ... find the symbols with "*"
181-
.filter(({ s }) => s.find(({ s }) => s === "*"))
182-
// ... and extract the symbol
183-
.map(
184-
({ s }) => s,
185-
)
186-
.flat()
187-
// Remove duplicates
188-
.reduce<Symbol[]>(uniqueSymbols, [])
189-
.reduce<Array<[number, number]>>(
190-
(gears, symbol) => {
167+
return (
168+
parts
169+
// ... find the symbols with "*"
170+
.filter(({ s }) => s.find(({ s }) => s === "*"))
171+
// ... and extract the symbol
172+
.map(({ s }) => s)
173+
.flat()
174+
// Remove duplicates
175+
.reduce<Symbol[]>(uniqueSymbols, [])
176+
.reduce<Array<[number, number]>>((gears, symbol) => {
191177
// Find the part numbers with this symbol
192178
const matchingParts = parts.filter(({ s }) =>
193179
s.find(({ col, row }) => col === symbol.col && row === symbol.row)
194180
);
195181
return matchingParts.length === 2
196182
? [...gears, matchingParts.map(({ n }) => n) as [number, number]]
197183
: gears;
198-
},
199-
[],
200-
);
184+
}, [])
185+
);
201186
};
202187

203188
const uniqueSymbols = (symbols: Symbol[], symbol: Symbol) => {
204189
if (
205190
symbols.find(({ col, row }) => col === symbol.col && row === symbol.row) ===
206-
undefined
191+
undefined
207192
) {
208193
return [...symbols, symbol];
209194
}

day04_test.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { assertEquals } from "https://deno.land/std@0.208.0/assert/assert_equals.ts";
22
import { sum } from "./util/sum.ts";
3+
import { toNumber } from "./util/toNumber.ts";
34

45
const card1 = `Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53`;
56
const card2 = `Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19`;
@@ -88,20 +89,8 @@ const parseCard = (cardInfo: string): CardInfo => {
8889
const [, winningNumbers, cardNumbers] =
8990
/^Card +\d+: ([\d ]+)+ \| ([\d ]+)+/.exec(cardInfo) ?? [];
9091
return [
91-
new Set(
92-
winningNumbers
93-
.trim()
94-
.replace(/ +/g, " ")
95-
.split(" ")
96-
.map((s) => parseInt(s, 10))
97-
),
98-
new Set(
99-
cardNumbers
100-
.trim()
101-
.replace(/ +/g, " ")
102-
.split(" ")
103-
.map((s) => parseInt(s, 10))
104-
),
92+
new Set(winningNumbers.trim().replace(/ +/g, " ").split(" ").map(toNumber)),
93+
new Set(cardNumbers.trim().replace(/ +/g, " ").split(" ").map(toNumber)),
10594
];
10695
};
10796

day05_test.ts

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { assertEquals } from "https://deno.land/std@0.208.0/assert/assert_equals.ts";
2+
import { toNumber } from "./util/toNumber.ts";
3+
4+
Deno.test("Day 5: If You Give A Seed A Fertilizer", async (t) => {
5+
await t.step("Part 1", async (t) => {
6+
await t.step("Example", async (t) => {
7+
await t.step("Map example", async (t) => {
8+
// Consider again the example seed-to-soil map:
9+
const seedToSoil = seedMap([
10+
/*
11+
The first line has a destination range start of 50, a source range start of 98, and a range length of 2. This line means that the source range starts at 98 and contains two values: 98 and 99. The destination range is the same length, but it starts at 50, so its two values are 50 and 51.
12+
*/
13+
`50 98 2`,
14+
/*
15+
The second line means that the source range starts at 50 and contains 48 values: 50, 51, ..., 96, 97. This corresponds to a destination range starting at 52 and also containing 48 values: 52, 53, ..., 98, 99.
16+
*/
17+
`52 50 48`,
18+
]);
19+
for (const [seed, soil] of [
20+
// With this information, you know that seed number 98 corresponds to soil number 50 and that seed number 99 corresponds to soil number 51.
21+
[98, 50],
22+
[99, 51],
23+
// So, seed number 53 corresponds to soil number 55.
24+
[53, 55],
25+
// Any source numbers that aren't mapped correspond to the same destination number. So, seed number 10 corresponds to soil number 10.
26+
[10, 10],
27+
// With this map, you can look up the soil number required for each initial seed number:
28+
[79, 81],
29+
[14, 14],
30+
[55, 57],
31+
[13, 13],
32+
]) {
33+
await t.step(
34+
`Seed number ${seed} corresponds to soil number ${soil}`,
35+
() => assertEquals(seedToSoil(seed), soil)
36+
);
37+
}
38+
});
39+
40+
await t.step("Almanac example", async () => {
41+
const example = almanac(
42+
(await Deno.readTextFile("./input/day05.example.txt")).split("\n")
43+
);
44+
45+
/*
46+
Using these maps, find the lowest location number that corresponds to any of the initial seeds. To do this, you'll need to convert each seed number through other categories until you can find its corresponding location number. In this example, the corresponding types are:
47+
*/
48+
assertEquals(example, new Set([82, 43, 86, 35]));
49+
50+
// So, the lowest location number in this example is 35.
51+
assertEquals(lowest(example), 35);
52+
});
53+
});
54+
55+
await t.step("Solution", async (t) => {
56+
const solution = almanac(
57+
(await Deno.readTextFile("./input/day05.txt")).split("\n")
58+
);
59+
assertEquals(lowest(solution), 403695602);
60+
});
61+
});
62+
});
63+
64+
const rangeRx =
65+
/^(?<destRangeStart>\d+) (?<sourceRangeStart>\d+) (?<rangeLength>\d+)$/;
66+
67+
const seedMap = (ranges: string[]) => {
68+
// Parse the lines
69+
const r = ranges
70+
.map(
71+
(range) =>
72+
Object.values(rangeRx.exec(range)?.groups ?? {}).map((s) =>
73+
parseInt(s, 10)
74+
) as [number, number, number]
75+
)
76+
.map(([destRangeStart, sourceRangeStart, rangeLength]) => ({
77+
destRangeStart,
78+
sourceRangeStart,
79+
// Calculate the end of the range
80+
sourceRangeEnd: sourceRangeStart + rangeLength,
81+
rangeLength,
82+
}));
83+
return (seed: number): number => {
84+
// find a matching range
85+
const range = r.find(
86+
({ sourceRangeStart, sourceRangeEnd }) =>
87+
sourceRangeStart <= seed && sourceRangeEnd >= seed
88+
);
89+
if (range === undefined) return seed;
90+
return seed + range.destRangeStart - range.sourceRangeStart;
91+
};
92+
};
93+
94+
const almanac = (almanac: string[]) => {
95+
// Get the seeds from the first line
96+
const seeds = almanac[0].split(":")[1].trim().split(" ").map(toNumber);
97+
98+
// Read in all the map ranges
99+
const mapRanges: Array<Array<string>> = [[]];
100+
let i = 0;
101+
// Skip first three lines
102+
for (const line of almanac.slice(3)) {
103+
if (line === "") continue; // Ignore blank lines
104+
if (rangeRx.test(line) === false) {
105+
// Use non-range line to start a new map
106+
mapRanges[++i] = [];
107+
continue;
108+
}
109+
mapRanges[i].push(line);
110+
}
111+
112+
// Convert to maps
113+
const maps = mapRanges.map(seedMap);
114+
115+
// Pass each seed through each map
116+
return new Set<number>(
117+
seeds.map((seed) => maps.reduce((mapped, seedMap) => seedMap(mapped), seed))
118+
);
119+
};
120+
121+
const lowest = (numbers: Set<number>) => Math.min(...numbers.values());

input/day05.example.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
seeds: 79 14 55 13
2+
3+
seed-to-soil map:
4+
50 98 2
5+
52 50 48
6+
7+
soil-to-fertilizer map:
8+
0 15 37
9+
37 52 2
10+
39 0 15
11+
12+
fertilizer-to-water map:
13+
49 53 8
14+
0 11 42
15+
42 0 7
16+
57 7 4
17+
18+
water-to-light map:
19+
88 18 7
20+
18 25 70
21+
22+
light-to-temperature map:
23+
45 77 23
24+
81 45 19
25+
68 64 13
26+
27+
temperature-to-humidity map:
28+
0 69 1
29+
1 0 69
30+
31+
humidity-to-location map:
32+
60 56 37
33+
56 93 4

0 commit comments

Comments
 (0)