aoc/2023/day_03/solution.ts

411 lines
10 KiB
TypeScript
Raw Normal View History

2023-12-11 00:26:12 +00:00
/*
*/
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);
}