411 lines
10 KiB
TypeScript
411 lines
10 KiB
TypeScript
|
/*
|
||
|
|
||
|
*/
|
||
|
|
||
|
import { sum } from "../../_utils";
|
||
|
|
||
|
function isDigit(char: string): boolean {
|
||
|
return /^\d$/.test(char);
|
||
|
}
|
||
|
|
||
|
function isSymbol(char: string): boolean {
|
||
|
return !isDigit(char) && char !== ".";
|
||
|
}
|
||
|
|
||
|
function getCharAt(
|
||
|
m: string[],
|
||
|
row: number,
|
||
|
column: number,
|
||
|
fallback: string = "."
|
||
|
): string {
|
||
|
if (typeof m[row] !== "undefined") {
|
||
|
return m[row][column] || fallback;
|
||
|
} else {
|
||
|
return fallback;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export function part1_solver(lines: string[]): number {
|
||
|
// approach:
|
||
|
// 1. assume all numbers are invalid at first (pending)
|
||
|
// 2. continuously check the pending numbers to see if they can be moved to valid (a symbol is found)
|
||
|
// 3. once the iterator is out of range of a number, move it to invalid list
|
||
|
let valid_part_numbers = [];
|
||
|
|
||
|
lines.forEach((line, row) => {
|
||
|
// const elements = line.split(".");
|
||
|
let column = 0;
|
||
|
let buffer = "";
|
||
|
|
||
|
let is_valid = false;
|
||
|
while (column <= line.length) {
|
||
|
const current_char = line[column];
|
||
|
|
||
|
if (isDigit(current_char)) {
|
||
|
buffer += current_char;
|
||
|
|
||
|
// check previous row
|
||
|
const up_left = getCharAt(lines, row - 1, column - 1);
|
||
|
const up_mid = getCharAt(lines, row - 1, column);
|
||
|
const up_right = getCharAt(lines, row - 1, column + 1);
|
||
|
|
||
|
const left = getCharAt(lines, row, column - 1);
|
||
|
const right = getCharAt(lines, row, column + 1);
|
||
|
|
||
|
const down_left = getCharAt(lines, row + 1, column - 1);
|
||
|
const down_mid = getCharAt(lines, row + 1, column);
|
||
|
const down_right = getCharAt(lines, row + 1, column + 1);
|
||
|
|
||
|
if (!is_valid) {
|
||
|
is_valid =
|
||
|
isSymbol(up_left) ||
|
||
|
isSymbol(up_mid) ||
|
||
|
isSymbol(up_right) ||
|
||
|
isSymbol(left) ||
|
||
|
isSymbol(right) ||
|
||
|
isSymbol(down_left) ||
|
||
|
isSymbol(down_mid) ||
|
||
|
isSymbol(down_right);
|
||
|
}
|
||
|
|
||
|
if (isDigit(right)) {
|
||
|
column += 1;
|
||
|
continue;
|
||
|
} else if (isSymbol(right)) {
|
||
|
valid_part_numbers.push(buffer);
|
||
|
is_valid = false;
|
||
|
buffer = "";
|
||
|
} else {
|
||
|
if (is_valid) {
|
||
|
valid_part_numbers.push(buffer);
|
||
|
}
|
||
|
|
||
|
is_valid = false;
|
||
|
buffer = "";
|
||
|
}
|
||
|
} else {
|
||
|
buffer = "";
|
||
|
is_valid = false;
|
||
|
column += 1;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
column += 1;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return valid_part_numbers
|
||
|
.map((nstr) => +nstr)
|
||
|
.filter((n) => !isNaN(n))
|
||
|
.reduce(sum, 0);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
|
||
|
*/
|
||
|
type Element = {
|
||
|
kind: "number" | "gear";
|
||
|
value: string;
|
||
|
position: [number, number];
|
||
|
};
|
||
|
|
||
|
type Gear = {
|
||
|
n1: number;
|
||
|
n2: number;
|
||
|
};
|
||
|
|
||
|
const checkColumsFor = (
|
||
|
lines: string[],
|
||
|
row: number,
|
||
|
columns: number[]
|
||
|
): number[] => {
|
||
|
let matched_nums = [];
|
||
|
|
||
|
columns.forEach((column) => {
|
||
|
if (isDigit(getCharAt(lines, row, column))) {
|
||
|
// The target value was a digit. There are two cases:
|
||
|
// 1. The digit belongs to the same number we've seen before. E.g.
|
||
|
// ...123.......
|
||
|
// ....*........
|
||
|
// 2. The digit belongs to a new number on that row. E.g.
|
||
|
// ...123.456...
|
||
|
// ......*......
|
||
|
} else {
|
||
|
// The target value was not a digit, so ignore
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// columns.map(col => isDigit(getCharAt(lines, row, col))).every()
|
||
|
return columns.reduce((matched_nums, column) => {
|
||
|
if (isDigit(getCharAt(lines, row, column))) {
|
||
|
}
|
||
|
}, []);
|
||
|
};
|
||
|
|
||
|
const checkTopRow = (
|
||
|
lines: string[],
|
||
|
currentRow: number,
|
||
|
currentColumn: number
|
||
|
): number[] => {
|
||
|
const row = currentRow - 1;
|
||
|
const column = currentColumn;
|
||
|
let adjacent_nums = [];
|
||
|
|
||
|
const up_left = getCharAt(lines, row, currentColumn - 1);
|
||
|
const up_mid = getCharAt(lines, row, currentColumn);
|
||
|
const up_right = getCharAt(lines, row, currentColumn + 1);
|
||
|
|
||
|
if (isDigit(up_left) || isDigit(up_mid) || isDigit(up_right)) {
|
||
|
console.log("found on prev row");
|
||
|
// get all the nums from the previous row
|
||
|
const nums = (lines[row - 1] || "")
|
||
|
.split(".")
|
||
|
.filter((s) => s.length > 0)
|
||
|
.map((s) =>
|
||
|
s
|
||
|
.split("")
|
||
|
.filter((c) => isDigit(c))
|
||
|
.join("")
|
||
|
);
|
||
|
|
||
|
// figure out which num overlaps this gear
|
||
|
const res1 = nums.find((n) => {
|
||
|
const foundNumIndex = lines[row - 1].indexOf(n);
|
||
|
const lastDigit = foundNumIndex + n.length - 1;
|
||
|
|
||
|
console.log(JSON.stringify({ foundNumIndex, lastDigit, column }));
|
||
|
return (
|
||
|
foundNumIndex === column - 1 ||
|
||
|
foundNumIndex === column ||
|
||
|
foundNumIndex === column + 1 ||
|
||
|
lastDigit === column - 1 ||
|
||
|
lastDigit === column ||
|
||
|
lastDigit === column + 1 ||
|
||
|
(column - 1 >= foundNumIndex && column + 1 <= lastDigit)
|
||
|
);
|
||
|
});
|
||
|
|
||
|
if (res1) {
|
||
|
adjacent_nums.push(res1);
|
||
|
|
||
|
const res2 = nums.slice(nums.indexOf(res1)).find((n) => {
|
||
|
const foundNumIndex = lines[row - 1].indexOf(n);
|
||
|
const lastDigit = foundNumIndex + n.length - 1;
|
||
|
|
||
|
console.log(JSON.stringify({ foundNumIndex, lastDigit, column }));
|
||
|
return (
|
||
|
foundNumIndex === column - 1 ||
|
||
|
foundNumIndex === column ||
|
||
|
foundNumIndex === column + 1 ||
|
||
|
lastDigit === column - 1 ||
|
||
|
lastDigit === column ||
|
||
|
lastDigit === column + 1 ||
|
||
|
(column - 1 >= foundNumIndex && column + 1 <= lastDigit)
|
||
|
);
|
||
|
});
|
||
|
|
||
|
if (res2) {
|
||
|
adjacent_nums.push(res2);
|
||
|
}
|
||
|
|
||
|
console.log(
|
||
|
"res is " +
|
||
|
JSON.stringify({
|
||
|
res1,
|
||
|
res2,
|
||
|
nums,
|
||
|
last: lines[row - 1],
|
||
|
column,
|
||
|
})
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return adjacent_nums;
|
||
|
};
|
||
|
|
||
|
const checkCurrentRow = (
|
||
|
lines: string[],
|
||
|
currentRow: number,
|
||
|
currentColumn: number
|
||
|
): number[] => {
|
||
|
const row = currentRow - 1;
|
||
|
const column = currentColumn;
|
||
|
let adjacent_nums = [];
|
||
|
|
||
|
const left = getCharAt(lines, row, column - 1);
|
||
|
const right = getCharAt(lines, row, column + 1);
|
||
|
|
||
|
if (isDigit(left) || isDigit(right)) {
|
||
|
console.log("found on this row");
|
||
|
// get all the nums from this row
|
||
|
const nums = lines[row]
|
||
|
.split(".")
|
||
|
.filter((s) => s.length > 0)
|
||
|
.map((s) =>
|
||
|
s
|
||
|
.split("")
|
||
|
.filter((c) => isDigit(c))
|
||
|
.join("")
|
||
|
);
|
||
|
|
||
|
// figure out which num overlaps this gear
|
||
|
const res1 = nums.find((n) => {
|
||
|
const foundNumIndex = lines[row].indexOf(n);
|
||
|
const lastDigit = foundNumIndex + n.length - 1;
|
||
|
|
||
|
return (
|
||
|
foundNumIndex === column - 1 ||
|
||
|
foundNumIndex === column ||
|
||
|
foundNumIndex === column + 1 ||
|
||
|
lastDigit === column - 1 ||
|
||
|
lastDigit === column ||
|
||
|
lastDigit === column + 1 ||
|
||
|
(column - 1 >= foundNumIndex && column + 1 <= lastDigit)
|
||
|
);
|
||
|
});
|
||
|
|
||
|
if (res1) {
|
||
|
adjacent_nums.push(res1);
|
||
|
|
||
|
const res2 = nums.find((n) => {
|
||
|
const foundNumIndex = lines[row].indexOf(n);
|
||
|
const lastDigit = foundNumIndex + n.length - 1;
|
||
|
|
||
|
return (
|
||
|
foundNumIndex === column - 1 ||
|
||
|
foundNumIndex === column ||
|
||
|
foundNumIndex === column + 1 ||
|
||
|
lastDigit === column - 1 ||
|
||
|
lastDigit === column ||
|
||
|
lastDigit === column + 1 ||
|
||
|
(column - 1 >= foundNumIndex && column + 1 <= lastDigit)
|
||
|
);
|
||
|
});
|
||
|
|
||
|
if (res2) {
|
||
|
adjacent_nums.push(res2);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return adjacent_nums;
|
||
|
};
|
||
|
|
||
|
const checkBottomRow = (
|
||
|
lines: string[],
|
||
|
currentRow: number,
|
||
|
currentColumn: number
|
||
|
): number[] => {
|
||
|
const row = currentRow - 1;
|
||
|
const column = currentColumn;
|
||
|
let adjacent_nums = [];
|
||
|
|
||
|
const down_left = getCharAt(lines, row + 1, column - 1);
|
||
|
const down_mid = getCharAt(lines, row + 1, column);
|
||
|
const down_right = getCharAt(lines, row + 1, column + 1);
|
||
|
|
||
|
if (isDigit(down_left) || isDigit(down_mid) || isDigit(down_right)) {
|
||
|
console.log("found on next row");
|
||
|
// get all the nums from the next row
|
||
|
const nums = lines[row + 1]
|
||
|
.split(".")
|
||
|
.filter((s) => s.length > 0)
|
||
|
.map((s) =>
|
||
|
s
|
||
|
.split("")
|
||
|
.filter((c) => isDigit(c))
|
||
|
.join("")
|
||
|
);
|
||
|
|
||
|
// figure out which num overlaps this gear
|
||
|
const res1 = nums.find((n) => {
|
||
|
// [6 7 8 9]
|
||
|
// [0 1 2 3]
|
||
|
const foundNumIndex = lines[row + 1].indexOf(n);
|
||
|
const lastDigitIndex = foundNumIndex + n.length - 1;
|
||
|
|
||
|
return (
|
||
|
foundNumIndex === column - 1 ||
|
||
|
foundNumIndex === column ||
|
||
|
foundNumIndex === column + 1 ||
|
||
|
lastDigitIndex === column - 1 ||
|
||
|
lastDigitIndex === column ||
|
||
|
lastDigitIndex === column + 1 ||
|
||
|
(column - 1 >= foundNumIndex && column + 1 <= lastDigitIndex)
|
||
|
);
|
||
|
});
|
||
|
|
||
|
if (res1) {
|
||
|
adjacent_nums.push(res1);
|
||
|
|
||
|
const res2 = nums.find((n) => {
|
||
|
// [6 7 8 9]
|
||
|
// [0 1 2 3]
|
||
|
const foundNumIndex = lines[row + 1].indexOf(n);
|
||
|
const lastDigitIndex = foundNumIndex + n.length - 1;
|
||
|
|
||
|
return (
|
||
|
foundNumIndex === column - 1 ||
|
||
|
foundNumIndex === column ||
|
||
|
foundNumIndex === column + 1 ||
|
||
|
lastDigitIndex === column - 1 ||
|
||
|
lastDigitIndex === column ||
|
||
|
lastDigitIndex === column + 1 ||
|
||
|
(column - 1 >= foundNumIndex && column + 1 <= lastDigitIndex)
|
||
|
);
|
||
|
});
|
||
|
|
||
|
if (res2) {
|
||
|
adjacent_nums.push(res2);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return adjacent_nums;
|
||
|
};
|
||
|
|
||
|
export function part2_solver(lines: string[]): number {
|
||
|
let gears: Gear[] = [];
|
||
|
|
||
|
lines.forEach((line, row) => {
|
||
|
let column = 0;
|
||
|
let adjacent_nums = [];
|
||
|
|
||
|
while (column <= line.length) {
|
||
|
const current_char = line[column];
|
||
|
|
||
|
if (current_char === "*") {
|
||
|
const top_nums = checkTopRow(lines, row, column);
|
||
|
adjacent_nums.push(top_nums);
|
||
|
|
||
|
const current_nums = checkCurrentRow(lines, row, column);
|
||
|
adjacent_nums.push(current_nums);
|
||
|
|
||
|
const bottom_nums = checkBottomRow(lines, row, column);
|
||
|
adjacent_nums.push(bottom_nums);
|
||
|
|
||
|
console.log(JSON.stringify({ adjacent_nums }));
|
||
|
|
||
|
if (adjacent_nums.length === 2) {
|
||
|
console.log("found a gear!");
|
||
|
gears.push({ n1: +adjacent_nums[0], n2: +adjacent_nums[1] });
|
||
|
} else {
|
||
|
adjacent_nums = [];
|
||
|
}
|
||
|
} else {
|
||
|
adjacent_nums = [];
|
||
|
column += 1;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
column += 1;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return gears
|
||
|
.map((g) => g.n1 * g.n2)
|
||
|
.filter((n) => !isNaN(n))
|
||
|
.reduce(sum, 0);
|
||
|
}
|