Skip to content

Commit ee55eca

Browse files
committed
feat(day03): Part 2
1 parent edebb0c commit ee55eca

File tree

2 files changed

+119
-44
lines changed

2 files changed

+119
-44
lines changed

day03_test.ts

Lines changed: 118 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,66 +18,98 @@ const schematics = [
1818
];
1919

2020
Deno.test("Day 3: Gear Ratios", async (t) => {
21-
await t.step("Example", async (t) => {
22-
await t.step(
23-
"findPartNumbers()",
24-
() =>
25-
assertEquals(findPartNumbers(schematics), [
26-
{ n: 467, s: ["*"] },
27-
{ n: 114, s: [] },
28-
{ n: 35, s: ["*"] },
29-
{ n: 633, s: ["#"] },
30-
{ n: 617, s: ["*"] },
31-
{ n: 58, s: [] },
32-
{ n: 592, s: ["+"] },
33-
{ n: 755, s: ["*"] },
34-
{ n: 664, s: ["$"] },
35-
{ n: 598, s: ["*"] },
36-
{ n: 1, s: [] },
37-
]),
38-
);
21+
await t.step("Part 1", async (t) => {
22+
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+
]),
36+
);
37+
38+
await t.step(`Calculate the example solution`, () =>
39+
assertEquals(
40+
sum(
41+
findPartNumbers(schematics)
42+
.map(({ n }) => n),
43+
),
44+
4361,
45+
));
46+
});
3947

40-
await t.step(`Calculate the example solution`, () =>
48+
await t.step("Solution", async () => {
49+
const numbers = findPartNumbers(
50+
(await Deno.readTextFile("./input/day03.txt")).split("\n"),
51+
);
4152
assertEquals(
4253
sum(
43-
findPartNumbers(schematics)
44-
.filter(({ s }) => s.length > 0)
54+
numbers
4555
.map(({ n }) => n),
4656
),
47-
4361,
48-
));
57+
529618,
58+
);
59+
});
4960
});
5061

51-
await t.step("Solution", async () => {
52-
const numbers = findPartNumbers(
53-
(await Deno.readTextFile("./input/day03.txt")).split("\n"),
54-
);
55-
assertEquals(
56-
sum(
57-
numbers.filter(({ s }) => s.length > 0)
58-
.map(({ n }) => n),
59-
),
60-
529618,
61-
);
62+
/**
63+
* A gear is any * symbol that is adjacent to exactly two part numbers.
64+
* Its gear ratio is the result of multiplying those two numbers together.
65+
*
66+
* In this schematic, there are two gears. The first is in the top left;
67+
* it has part numbers 467 and 35, so its gear ratio is 16345. The second
68+
* gear is in the lower right; its gear ratio is 451490.
69+
*
70+
* Adding up all of the gear ratios produces 467835.
71+
*/
72+
await t.step("Part 2", async (t) => {
73+
await t.step(`Solve example`, () =>
74+
assertEquals(
75+
sum(
76+
findGears(schematics).map(([g1, g2]) => g1 * g2),
77+
),
78+
467835,
79+
));
80+
81+
await t.step("Solution", async () =>
82+
assertEquals(
83+
sum(
84+
findGears(
85+
(await Deno.readTextFile("./input/day03.txt")).split("\n"),
86+
).map(([g1, g2]) => g1 * g2),
87+
),
88+
77509019,
89+
));
6290
});
6391
});
6492

6593
/**
6694
* Find the part numbers in the schematics and the adjacent symbol(s)
95+
*
96+
* TODO: refactor to search for symbols first and then numbers. This will make the code work better for part 2.
6797
*/
6898
const findPartNumbers = (
6999
schematics: string[],
70-
): { n: number; s: string[] }[] => {
71-
const numbers: { n: number; s: string[] }[] = [];
100+
): { n: number; s: Symbol[] }[] => {
101+
const numbers: { n: number; s: Symbol[] }[] = [];
72102
let currentNumber: string[] = [];
73-
let adjacentSymbols: string[] = [];
103+
let adjacentSymbols: Symbol[] = [];
74104

75105
// Add parsed number with symbols to stash
76106
const addNumber = () => {
77107
if (currentNumber.length > 0) {
78108
numbers.push({
79109
n: parseInt(currentNumber.join("")),
80-
s: [...new Set(adjacentSymbols)],
110+
s: adjacentSymbols
111+
// Remove duplicate symbols
112+
.reduce<Symbol[]>(uniqueSymbols, []),
81113
});
82114
}
83115
currentNumber = [];
@@ -97,17 +129,19 @@ const findPartNumbers = (
97129
}
98130
}
99131
addNumber();
100-
return numbers;
132+
return numbers.filter(({ s }) => s.length > 0);
101133
};
102134

135+
type Symbol = { s: string; col: number; row: number };
136+
103137
/**
104138
* Find adjacent symbols for a given field
105139
*/
106140
const findAdjacentSymbols = (
107141
schematics: string[],
108142
row: number,
109143
col: number,
110-
): string[] =>
144+
): Symbol[] =>
111145
[
112146
findSymbolAt(schematics, row, col - 1), // left
113147
findSymbolAt(schematics, row - 1, col - 1), // top left
@@ -117,7 +151,7 @@ const findAdjacentSymbols = (
117151
findSymbolAt(schematics, row + 1, col + 1), // bottom right
118152
findSymbolAt(schematics, row + 1, col), // bottom
119153
findSymbolAt(schematics, row + 1, col - 1), // bottom left
120-
].filter((s) => s !== undefined) as string[];
154+
].filter<Symbol>((s): s is Symbol => s?.s !== undefined);
121155

122156
/**
123157
* Return the symbol at a given position
@@ -126,11 +160,52 @@ const findSymbolAt = (
126160
schematics: string[],
127161
row: number,
128162
col: number,
129-
): string | undefined => {
163+
): Symbol | undefined => {
130164
if (col < 0) return undefined; // Outside of schematics
131165
if (row < 0) return undefined; // Outside of schematics
132166
const c = schematics[row]?.[col];
133167
if (/\d/.test(c)) return undefined; // a number
134168
if (c === ".") return undefined; // a dot, ignore
135-
return c;
169+
return {
170+
s: c,
171+
col,
172+
row,
173+
};
174+
};
175+
176+
const findGears = (schematics: string[]) => {
177+
const parts = findPartNumbers(schematics);
178+
// 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) => {
191+
// Find the part numbers with this symbol
192+
const matchingParts = parts.filter(({ s }) =>
193+
s.find(({ col, row }) => col === symbol.col && row === symbol.row)
194+
);
195+
return matchingParts.length === 2
196+
? [...gears, matchingParts.map(({ n }) => n) as [number, number]]
197+
: gears;
198+
},
199+
[],
200+
);
201+
};
202+
203+
const uniqueSymbols = (symbols: Symbol[], symbol: Symbol) => {
204+
if (
205+
symbols.find(({ col, row }) => col === symbol.col && row === symbol.row) ===
206+
undefined
207+
) {
208+
return [...symbols, symbol];
209+
}
210+
return symbols;
136211
};

util/sum.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
export const sum = (numbers: Array<number>): number =>
2-
numbers.reduce((sum, number) => sum + number);
2+
numbers.length === 0 ? 0 : numbers.reduce((sum, number) => sum + number);

0 commit comments

Comments
 (0)