이 글에서는 두 개의 2차원 숫자 배열(행렬)을 인자로 받아 행렬 곱셈 결과를 반환하는 JavaScript 함수를 작성하는 방법을 알아봅니다.
행렬 곱셈의 기본 원리
행렬 곱셈은 첫 번째 행렬의 열 개수와 두 번째 행렬의 행 개수가 같을 때만 가능합니다. 예를 들어 X×Z 크기의 행렬과 Z×Y 크기의 행렬을 곱하면 X×Y 크기의 결과 행렬이 만들어집니다.
먼저 예제로 사용할 두 개의 행렬을 살펴보겠습니다.
// 5 x 4 크기의 행렬 a
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 크기의 행렬 b
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]
];행렬 곱셈 함수 구현하기
아래 함수는 먼저 입력값이 유효한 2차원 배열인지 검사하고, 두 행렬의 차원이 곱셈 조건을 만족하는지 확인합니다. 이후 삼중 반복문을 사용해 각 위치의 값을 계산합니다.
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; // 두 번째 행렬의 열 개수
if (b.length !== z) {
// XxZ * ZxY => XxY 규칙 검증
throw new Error('첫 번째 행렬의 열 개수는 두 번째 행렬의 행 개수와 같아야 합니다');
}
// 결과를 담을 x x y 크기의 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));실행 결과
위 코드를 실행하면 콘솔에 다음과 같이 5×6 크기의 결과 행렬이 출력됩니다.
[
[ 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 ]
]코드 설명
핵심 로직은 세 개의 중첩 반복문입니다. 외부의 두 반복문(i, j)은 결과 행렬의 각 좌표를 순회하고, 내부 반복문(k)은 해당 좌표의 값을 계산하기 위해 첫 번째 행렬의 i번째 행과 두 번째 행렬의 j번째 열의 요소들을 곱한 뒤 모두 더합니다.
또한 이 함수는 다음과 같은 안정성 장점을 가집니다.
- 입력값 검증: 인자가 배열이 아니거나 비어 있으면 에러를 발생시켜 잘못된 호출을 조기에 잡아냅니다.
- 차원 호환 검사: 곱셈이 수학적으로 불가능한 경우 명확한 에러 메시지로 알려줍니다.
- 독립적인 초기화: productRow.slice()를 사용해 각 행을 별도의 배열로 복제하므로, 참조 공유로 인한 값 오염 문제를 방지합니다.
이 방식의 시간 복잡도는 O(x·y·z)로, 일반적인 행렬 곱셈 알고리즘과 동일합니다. 작은 규모의 행렬 연산에는 충분히 효율적이며, 그래픽 변환이나 데이터 처리 등 다양한 상황에서 활용할 수 있습니다.