Computer >> 컴퓨터 >  >> 프로그래밍 >> JavaScript

JavaScript로 차원이 다른 두 행렬의 곱셈 구현하기

개요

행렬 곱셈은 선형대수학의 기본 연산으로, 그래픽 처리, 머신러닝, 게임 개발 등 다양한 분야에서 활용됩니다. 이번 글에서는 숫자로 이루어진 두 개의 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 같은 라이브러리 활용을 고려해 보세요.