https://school.programmers.co.kr/learn/courses/30/lessons/12949
학교에서 행렬을 배웠던 기억이 난다.
그땐 "뭐 이런 게 다 있어. 불편해 죽겠네."라고 생각했었다.
그래서 그렇게 깊게 공부하진 못했던 것 같다.
이제 와서야 행렬이 대단히 중요한 개념임을 인식은 한다만...
행렬의 덧셈과 뺄셈은 간단했다.
그냥 눈에 보이는 대로 계산만 하면 됐다.
곱셈은 확실히 귀찮았다.
행렬의 사이즈가 커지면 정말 재앙이 따로 없었다.
그런 기억을 떠올리며 코드를 써 보자.
먼저 일반적인 형태부터.
function multMat(matA, matB) {
let result = [];
let rowsA = matA.length;
let colsA = matA[0].length;
let colsB = matB[0].length;
// result 초기화
for (let i = 0; i < rowsA; i++) {
result[i] = [];
for (let j = 0; j < colsB; j++) {
result[i][j] = 0;
}
}
// 행렬 곱셈 연산
for (let i = 0; i < rowsA; i++) {
for (let j = 0; j < colsB; j++) {
for (let k = 0; k < colsA; k++) {
result[i][j] += matA[i][k] * matB[k][j];
}
}
}
return result;
}
function solution(arr1, arr2) {
return multMat(arr1, arr2);
}
그렇다. 예전엔 무식하게 반복문을 때려 박으며 행렬곱을 구했다.
``for``문을 따라가다 보면 괜히 내가 잘 따라가고 있는가 하는 의구심도 들곤 한다.
역시 3중 반복문은 눈에 잘 들어오지 않는다.
이 과정을 단 몇 줄로 간단하게 해결할 수 있다면 어떨까?
function multMat(matA, matB) {
return matA.map(rowA =>
matB[0].map((_, i) =>
rowA.reduce((sum, val, j) => sum + val * matB[j][i], 0)
)
);
}
function solution(arr1, arr2) {
return multMat(arr1, arr2);
}
JS에서 ``map``과 ``reduce``는 정말 많이 사용되는 것 같다.
코드가 이렇게 간결해져 가독성이 좋아지기 때문이 아닐까?
행렬 자체는 2차원 배열이기 때문에 2개의 ``map()``이 사용됐다.
실제 배열을 구성할 값들은 ``reduce()``를 통해 계산해 만든다.
그럼 math.js와 같은 라이브러리도 유지보수를 위해 reduce()를 사용하지 않았을까?라는 추측이 가능하다.
다음은 math.js의 행렬곱 연산 로직 중 일부이다.
// loop matrix a rows
for (let i = 0; i < arows; i++) {
// current row
const row = adata[i]
// initialize row array
c[i] = []
// loop matrix b columns
for (let j = 0; j < bcolumns; j++) {
// sum (avoid initializing sum to zero)
let sum = mf(row[0], bdata[0][j])
// loop matrix a columns
for (let x = 1; x < acolumns; x++) {
// multiply & accumulate
sum = af(sum, mf(row[x], bdata[x][j]))
}
c[i][j] = sum
}
}
// return matrix
return a.createDenseMatrix({
data: c,
size: [arows, bcolumns],
datatype: adt === a._datatype && bdt === b._datatype ? dt : undefined
})
``reduce()``를 사용하지 않고 전통적인 ``for``루프를 사용해 구현했다.
왜 그럴까? ``reduce()``를 사용하면 보기도 좋은데 말이지.
이는 둘의 동작 방식의 차이에서 기인한다.
``reduce()``는 내부적으로 콜백 함수를 호출하기 때문에,
함수 호출에 따른 오버헤드가 발생한다.
보다 로우 레벨에서 동작하는 ``for``루프가 성능 상 이점이 있다는 뜻이다.
행렬이 작다면 둘 사이의 유의미한 차이는 없겠지만, 만약 엄청난 사이즈를 가진다면?
``for``루프가 ``reduce()``보다 성능 상 더 좋을 수 있다.
이러한 패키지는 어떤 값을 받을지 모르기 때문에 성능을 우선으로 두고 작성한 것 같다고 추측할 수 있다.
하지만 일반적인 상황에선 ``reduce()``를 통한 간결한 코드가 더 좋을 수도 있다.
결국 코드 작성도 사람이 하는 일이니 사람이 보기 좋아야 나중에 편하지 않을까.
'Camp > T.I.L.' 카테고리의 다른 글
[TIL #13] 피로도 (0) | 2024.09.20 |
---|---|
[TIL #12] 의상 (0) | 2024.09.12 |
[TIL #10] n^2 배열 자르기 (0) | 2024.09.11 |
[TIL #9] 로그인 / 비로그인 구분 (0) | 2024.09.10 |
[TIL #8] H-Index 구하기 (0) | 2024.09.09 |