/* */ 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); }