개요
행렬 곱셈은 선형대수학의 기본 연산으로, 그래픽 처리, 머신러닝, 게임 개발 등 다양한 분야에서 활용됩니다. 이번 글에서는 숫자로 이루어진 두 개의 2차원 배열(행렬)을 입력받아 행렬 곱셈 결과를 반환하는 JavaScript 함수를 작성해 보겠습니다.
행렬 곱셈이 성립하려면 첫 번째 행렬의 열 개수와 두 번째 행렬의 행 개수가 같아야 한다는 점에 유의해야 합니다. 예를 들어 X×Z 크기의 행렬과 Z×Y 크기의 행렬을 곱하면 결과는 X×Y 크기의 행렬이 됩니다.
예제 행렬 준비
이번 예제에서는 서로 차원이 다른 두 행렬을 사용합니다. 하나는 5×4 크기, 다른 하나는 4×6 크기입니다.
// 5 x 4 행렬 let a = [ [1, 2, 3, 1], [4, 5, 6, 1], [7, 8, 9, 1], [1, 1, 1, 1], [5, 7, 2, 6] ]; // 4 x 6 행렬 let b = [ [1, 4, 7, 3, 4, 6], [2, 5, 8, 7, 3, 2], [3, 6, 9, 6, 7, 8], [1, 1, 1, 2, 3, 6] ];
첫 번째 행렬의 열 개수(4)와 두 번째 행렬의 행 개수(4)가 일치하므로, 곱셈 결과는 5×6 크기의 행렬이 됩니다.
행렬 곱셈 함수 구현
먼저 입력값의 유효성을 검사하고, 삼중 반복문을 사용해 각 위치의 값을 계산하는 함수를 작성합니다.
const multiplyMatrices = (a, b) => {
// 입력값이 2차원 배열인지 검증
if (!Array.isArray(a) || !Array.isArray(b) || !a.length || !b.length) {
throw new Error('인수는 2차원 배열 형식이어야 합니다');
}
let x = a.length, // 첫 번째 행렬의 행 개수
z = a[0].length, // 첫 번째 행렬의 열 개수
y = b[0].length; // 두 번째 행렬의 열 개수
// 곱셈 가능 여부 검증: XxZ * ZxY => XxY
if (b.length !== z) {
throw new Error('첫 번째 행렬의 열 개수는 두 번째 행렬의 행 개수와 같아야 합니다');
}
// 결과 행렬을 0으로 초기화
let productRow = Array.apply(null, new Array(y)).map(Number.prototype.valueOf, 0);
let product = new Array(x);
for (let p = 0; p < x; p++) {
product[p] = productRow.slice();
}
// 삼중 반복문으로 곱셈 수행
for (let i = 0; i < x; i++) {
for (let j = 0; j < y; j++) {
for (let k = 0; k < z; k++) {
product[i][j] += a[i][k] * b[k][j];
}
}
}
return product;
}
// 5 x 4 행렬
let a = [
[1, 2, 3, 1],
[4, 5, 6, 1],
[7, 8, 9, 1],
[1, 1, 1, 1],
[5, 7, 2, 6]
];
// 4 x 6 행렬
let b = [
[1, 4, 7, 3, 4, 6],
[2, 5, 8, 7, 3, 2],
[3, 6, 9, 6, 7, 8],
[1, 1, 1, 2, 3, 6]
];
// 5 x 6 크기의 행렬이 출력되어야 합니다
console.log(multiplyMatrices(a, b));코드 동작 원리
함수의 핵심 로직은 다음과 같습니다.
1. 유효성 검사: 입력값이 비어 있지 않은 배열인지 확인하고, 두 행렬의 곱셈 조건(첫 번째 행렬의 열 개수 === 두 번째 행렬의 행 개수)이 충족되는지 검사합니다. 조건이 맞지 않으면 에러를 발생시켜 잘못된 연산을 사전에 차단합니다.
2. 결과 행렬 초기화: 최종 결과의 크기(X×Y)에 맞춰 모든 요소가 0인 2차원 배열을 미리 생성합니다.
3. 삼중 반복문: 결과 행렬의 각 위치 (i, j)에 대해, 첫 번째 행렬의 i번째 행과 두 번째 행렬의 j번째 열의 대응 요소들을 곱한 뒤 모두 더합니다. 이것이 행렬 곱셈의 정의 그 자체입니다.
실행 결과
콘솔 출력 결과는 다음과 같습니다.
[ [ 15, 33, 51, 37, 34, 40 ], [ 33, 78, 123, 85, 76, 88 ], [ 51, 123, 195, 133, 118, 136 ], [ 7, 16, 25, 18, 17, 22 ], [ 31, 73, 115, 88, 73, 96 ] ]
예상대로 5×6 크기의 행렬이 정상적으로 반환된 것을 확인할 수 있습니다.
마무리
이처럼 JavaScript에서도 별도의 라이브러리 없이 순수 코드만으로 행렬 곱셈을 구현할 수 있습니다. 위 구현의 시간 복잡도는 O(X·Y·Z)로, 행렬의 크기가 커질수록 연산량이 세제곱 수준으로 증가합니다. 대규모 행렬 연산이 필요하다면 GPU 가속을 지원하는 TensorFlow.js 같은 라이브러리 활용을 고려해 보세요.