Advent of Code 2025 Day 6

Advent of Code 2025 Day 6

오랜만에 올리네요. 현실이 바빠서 늦게 올렸습니다...
저는 항상 문자열 구현 문제가 번거롭다고 느껴지더라구요.
그래도 풀어봤습니다.

Part 1

--- 6일차: 쓰레기 압축기 ---
주방에서 엘프들을 도운 후 잠시 휴식을 취하며 영화 장면을 재연하는 것을 돕고 있었는데, 너무 열정적으로 쓰레기 슈트에 뛰어들었어요!

잠시 후, 쓰레기통에 갇히게 됩니다. 안타깝게도 문은 자석으로 봉인되어 있습니다.

탈출구를 찾으려다 두족류 가족이 다가옵니다! 두족류는 문을 열 수 있다고 확신하지만 시간이 좀 걸릴 것입니다. 기다리는 동안 막내 두족류의 수학 숙제를 도와줄 수 있는지 궁금해합니다.

두족류 수학은 일반 수학과 크게 다르지 않습니다. 수학 워크시트(퍼즐 입력)는 문제 목록으로 구성되며, 각 문제에는 (+) 또는 (*)을 더해야 하는 숫자 그룹이 있습니다.

그러나 문제들이 조금 이상하게 배열되어 있습니다. 매우 긴 가로 목록으로 나란히 표시되는 것 같습니다. 예를 들어:

123 328  51 64
 45 64  387 23
  6 98  215 314
*   +   *   +
각 문제의 숫자는 수직으로 배열되어 있으며, 문제 하단에는 수행해야 하는 작업의 기호가 있습니다. 문제는 공백으로만 이루어진 전체 열로 구분됩니다. 각 문제 내에서 숫자의 좌우 정렬은 무시할 수 있습니다.

그래서 이 워크시트에는 네 가지 문제가 포함되어 있습니다:

123 * 45 * 6 = 33210
328 + 64 + 98 = 490
51 * 387 * 215 = 4243455
64 + 23 + 314 = 401
작업을 확인하기 위해 두족류 학생들은 개별 문제에 대한 모든 답을 합산한 총합을 얻게 됩니다. 이 워크시트에서 총합은 33210 + 490 + 4243455 + 401 = 4277556입니다.

물론 실제 워크시트는 훨씬 더 넓습니다. 문제를 명확하게 읽을 수 있도록 완전히 풀어야 합니다.

수학 워크시트의 문제를 풀어보세요. 개별 문제에 대한 모든 답을 합산하여 얻은 총합은 얼마인가요?

정답

/**
 *
 * @param {string} input
 */
function main(input) {
  const lines = input
    .trim()
    .split("\n")
    .map((l) => l.trim().replaceAll(/\s+/g, " ").split(" "));
  const oper = lines.pop();

  let answer = 0;
  const switchedLine = [];
  lines.forEach((line, i) => {
    line.forEach((num, j) => {
      if (!switchedLine[j]) switchedLine[j] = [];
      switchedLine[j][i] = num;
    });
  });

  oper.forEach((op, i) => {
    const nums = switchedLine[i].map((n) => Number(n));

    if (op === "+") {
      answer += nums.reduce((a, b) => a + b, 0);
    } else if (op === "*") {
      answer += nums.reduce((a, b) => a * b, 1);
    }
  });

  return answer;
}

const input = `123 328  51 64 
 45 64  387 23 
  6 98  215 314
*   +   *   + `;

console.time("main");

const answer = main(input);
console.log("answer:", answer);

console.timeEnd("main");

해설

입력을 줄 단위로 파싱한 후, 행과 열을 전치(transpose)하여 각 열을 하나의 문제로 만듭니다.
마지막 줄의 연산자에 따라 각 열의 숫자들을 덧셈 또는 곱셈으로 계산하고, 모든 문제의 답을 합산해서 해결했습니다.

Part 2

--- 파트 2 ---
큰 두족류는 상황이 어떻게 진행되고 있는지 확인하기 위해 돌아옵니다. 워크시트에서 예상한 총합과 일치하지 않는 것을 보고 두족류 수학을 읽는 방법을 설명하는 것을 잊어버렸다는 사실을 깨닫게 됩니다.

두족류 수학은 오른쪽에서 왼쪽으로 열로 작성됩니다. 각 숫자는 고유한 열에 주어지며, 가장 큰 숫자는 상단에, 가장 작은 숫자는 하단에 표시됩니다. (문제는 여전히 공백으로만 구성된 열로 분리되어 있으며, 문제 하단의 기호는 여전히 사용할 연산자입니다.)

여기 다시 예제 워크시트가 있습니다:

123 328  51 64
 45 64  387 23
  6 98  215 314
*   +   *   +
문제들을 한 칸씩 오른쪽에서 왼쪽으로 읽으면서, 이제 문제들이 상당히 다릅니다:

가장 오른쪽 문제는 4 + 431 + 623 = 1058입니다
오른쪽에서 두 번째 문제는 175 * 581 * 32 = 3253600입니다
오른쪽에서 세 번째 문제는 8 + 248 + 369 = 625입니다
마지막으로, 가장 왼쪽 문제는 356 * 24 * 1 = 8544입니다
이제 총합은 1058 + 3253600 + 625 + 8544 = 3263827입니다.

수학 워크시트의 문제를 다시 풀어보세요. 개별 문제에 대한 모든 답을 합산하여 얻은 총합은 얼마입니까?

정답

/**
 * Part 2: 두족류 수학은 오른쪽에서 왼쪽으로 열로 작성됩니다.
 * 각 열을 문자 단위로 오른쪽에서 왼쪽으로 읽어서 숫자를 구성해야 합니다.
 * 입력 전처리에 주의: 공백 처리와 열 구분이 중요합니다.
 *
 * @param {string} input
 */
function main(input) {
  const _rawLines = input.trim().split("\n");
  const opers = _rawLines
    .pop()
    .split("")
    .filter((o) => o !== " ");

  const rawLines = _rawLines.map((str) => str.split(""));

  const len = rawLines[0].length;

  let start = 0;
  let sum = 0;
  for (let i = 0; i < len; i++) {
    // 전부다 빈 문자열이라면 다음.
    const isSplit = rawLines.filter((rawLine) => rawLine[i] === " ").length === rawLines.length;

    let nums = [];

    if (isSplit) {
      nums = _rawLines.map((line) => line.slice(start, i).replaceAll(" ", 0));

      start = i + 1;
    } else if (i === len - 1) {
      nums = _rawLines.map((line) => line.slice(start).replaceAll(" ", 0));
    }

    if (nums.length > 0) {
      const oper = opers.shift();
      const numLen = nums[0].length;
      const newNums = [];

      // 한자리씩 떼서 숫자만들기
      for (let j = 0; j < numLen; j++) {
        let newNum = "";
        for (let k = 0; k < nums.length; k++) {
          if (nums[k][j] !== "0") {
            newNum += nums[k][j];
          }
        }
        newNums.push(newNum);
      }

      if (oper === "*") {
        sum += newNums.reduce((p, c) => Number(p) * Number(c), 1);
      } else if (oper === "+") {
        sum += newNums.reduce((p, c) => Number(p) + Number(c), 0);
      }
    }
  }

  return sum;
}

const input = `123 328  51 64 
 45 64  387 23 
  6 98  215 314
*   +   *   + `;

console.time("main");

const answer = main(input);
console.log("answer:", answer);

console.timeEnd("main");

해설

Part 1과 달리 이번에는 각 열을 문자 단위로 세로로 읽어서 숫자를 만들어야 하는 문제였어요.

예를 들어 첫 번째 열을 보면:

이렇게 세로로 읽어서 146이 아니라, 각 행의 문자를 세로로 합쳐서 숫자를 만드는 거죠. 첫 번째 열에서 첫 번째 자리수는 1, 두 번째 자리수는 4, 세 번째 자리수는 6이니까 146이 되는 거고요.

입력 전처리가 좀 까다로웠는데, 공백으로 열이 구분되어 있어서 열 구분자를 찾는 로직이 필요했어요. 그리고 다행히 숫자 중에 0이 포함된 게 없어서 공백을 0으로 처리해도 문제가 없었습니다. 만약 0이 있었다면 좀 더 복잡했을 것 같아요!

전체적으로는 문자열 파싱과 인덱싱에 신경 쓰는 구현 문제였던 것 같아요. 코드가 좀 길어지긴 했지만, 단계별로 나눠서 생각하면 크게 어렵지는 않았습니다.