162 lines
4.1 KiB
TypeScript
162 lines
4.1 KiB
TypeScript
type PartInfo = {
|
|
partNumber: number;
|
|
begin: number;
|
|
end: number;
|
|
y: number;
|
|
}
|
|
|
|
function nextPartNumber(line: string, pos: number, y: number): PartInfo | null {
|
|
const numbers: string[] = [];
|
|
// eat characters until a number is found
|
|
while (pos < line.length) {
|
|
const ch = line[pos];
|
|
if (/\d/.test(ch)) {
|
|
break;
|
|
}
|
|
pos++;
|
|
}
|
|
// collect numbers
|
|
while (pos < line.length) {
|
|
const ch = line[pos];
|
|
if (!/\d/.test(ch)) {
|
|
break;
|
|
}
|
|
numbers.push(ch);
|
|
pos++;
|
|
}
|
|
if (numbers.length === 0) {
|
|
return null;
|
|
}
|
|
const partNumber = parseInt(numbers.join(""))
|
|
return {
|
|
partNumber,
|
|
begin: pos - numbers.length,
|
|
end: pos - 1, // inclusive
|
|
y,
|
|
}
|
|
}
|
|
|
|
function* queryNeighbors(x: number, y: number, boxWidth: number, width: number, height: number) {
|
|
boxWidth = Math.min(boxWidth, width - x);
|
|
if (x > 0) {
|
|
if (y > 0) {
|
|
yield [x - 1, y - 1];
|
|
}
|
|
yield [x - 1, y];
|
|
if (y < height - 1) {
|
|
yield [x - 1, y + 1];
|
|
}
|
|
}
|
|
if (x + boxWidth < width) {
|
|
if (y > 0) {
|
|
yield [x + boxWidth, y - 1];
|
|
}
|
|
yield [x + boxWidth, y];
|
|
if (y < height - 1) {
|
|
yield [x + boxWidth, y + 1];
|
|
}
|
|
}
|
|
if (y > 0) {
|
|
for (let i = x; i < x + boxWidth; i++) {
|
|
yield [i, y - 1];
|
|
}
|
|
}
|
|
if (y < height - 1) {
|
|
for (let i = x; i < x + boxWidth; i++) {
|
|
yield [i, y + 1];
|
|
}
|
|
}
|
|
}
|
|
|
|
function checkValidPartNumber(lines: string[], info: PartInfo) {
|
|
const neighbors = queryNeighbors(info.begin, info.y, info.end - info.begin + 1, lines[0].length, lines.length);
|
|
for (const [x, y] of neighbors) {
|
|
const line = lines[y];
|
|
const ch = line[x];
|
|
if ((!/\d/.test(ch)) && ch !== ".") {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function* iterPartNumbers(lines: string[]) {
|
|
let pos = 0;
|
|
let y = 0;
|
|
while (y < lines.length) {
|
|
const line = lines[y];
|
|
while (pos < line.length) {
|
|
const part = nextPartNumber(line, pos, y);
|
|
if (!part) {
|
|
break;
|
|
}
|
|
pos = part.end + 1;
|
|
// check valid part number
|
|
if (!checkValidPartNumber(lines, part)) {
|
|
continue;
|
|
}
|
|
yield part;
|
|
}
|
|
pos = 0;
|
|
y++;
|
|
}
|
|
}
|
|
|
|
// const example = `467..114..
|
|
// ...*......
|
|
// ..35..633.
|
|
// ......#...
|
|
// 617*......
|
|
// .....+.58.
|
|
// ..592.....
|
|
// ......755.
|
|
// ...$.*....
|
|
// .664.598..`;
|
|
const example = await Deno.readTextFile("input.txt");
|
|
const lines = example.split("\n").map(x => x.trim()).filter(x => x.length > 0);
|
|
const width = lines[0].length;
|
|
const height = lines.length;
|
|
|
|
console.log(width, height);
|
|
|
|
type GearCandidate = {
|
|
x: number;
|
|
y: number;
|
|
id: number;
|
|
neighbors: PartInfo[];
|
|
}
|
|
|
|
const gearCandidates: GearCandidate[] = [];
|
|
const coordinateToGearIndex = new Map<string, number>();
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
for (let j = 0; j < line.length; j++) {
|
|
if (line[j] === "*") {
|
|
const index = gearCandidates.length;
|
|
coordinateToGearIndex.set(`${j},${i}`, index);
|
|
gearCandidates.push({
|
|
x: j,
|
|
y: i,
|
|
id: index,
|
|
neighbors: [],
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const part of iterPartNumbers(lines)) {
|
|
const { begin, end, y } = part;
|
|
const neighbors = queryNeighbors(begin, y, end - begin + 1, width, height);
|
|
for (const [x, y] of neighbors) {
|
|
const id = coordinateToGearIndex.get(`${x},${y}`);
|
|
if (id !== undefined) {
|
|
gearCandidates[id].neighbors.push(part);
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(gearCandidates);
|
|
console.log(gearCandidates.filter(c => c.neighbors.length == 2).map(c=> c.neighbors[0].partNumber * c.neighbors[1].partNumber).reduce((a, b) => a + b, 0));
|
|
// console.log([...queryNeighbors(7, 1, 3, width, height)]
|
|
// .map(n => lines[n[1]][n[0]])
|
|
// )
|