개발조각

[프로그래머스] 행렬의 곱셈 본문

알고리즘🅰/프로그래머스

[프로그래머스] 행렬의 곱셈

개발조각 2022. 4. 21. 14:28
728x90
반응형

행렬의 곱셈이라... 행렬이 제가 고등학교 올라갔을 때 사라져서 배운 기억이 없어요.😅

그래서 검색해서 행렬의 곱셈이 뭔지 공부하고 풀었습니다.

 

행렬의 곱셈이 뭔지 아시면 쉽게 풀었을 것 같아요.

 


행렬의 곱셈이란

위키백과에 나와있는 행렬의 곱셈이란

행렬 곱셈(matrix multiplication)은 두 개의 행렬에서 한 개의 행렬을 만들어내는 이항 연산이다. 
이때 첫째 행렬의 열 개수와 둘째 행렬의 행 개수가 동일해야 한다. 
곱셈의 결과 새롭게 만들어진 행렬은 행렬곱(matrix product)라 하며, 첫째 행렬의 행 개수와 둘째 행렬의 열 개수를 가진다. 
행렬 A와 B의 곱은 간단히 AB로 나타낸다.

이렇게 씌여져 있긴 한데 저는 이걸 봐서는 행렬 곱셈이 뭔지 이해가 안 돼서 다른 자료를 찾아보았습니다.🙂

 

 

 

저는 행렬의 곱셈이 뭔지 아래 링크로 알게 되었습니다.👇

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=junhyuk7272&logNo=50128686426 

 

(2013수정)2강 행렬의 곱셈

1. 행렬의 곱셈 행렬의 곱셈은 계산 방법이 매우 독특하므로 여러번 반복해서 잘 익혀두어야 한다. 그 과정...

blog.naver.com

행렬의 곱셈은 이게 전부입니다.

이걸로 봐서 이해가 안 되시는 분들을 위해

제가 행렬의 곱셉 문제에 나와 있는 테스트로 손으로 써가면서 행렬의 곱셈을 해보았습니다.

(악필 주의)

 

테스트 1 

arr1 = [[1, 4], [3, 2], [4, 1]]
arr2 = [[3, 3], [3, 3]]
return =  [[15, 15], [15, 15], [15, 15]]

 

테스트 2

arr1 = [[2, 3, 2], [4, 2, 4], [3, 1, 4]]
arr2 = [[5, 4, 3], [2, 4, 1], [3, 1, 1]]
return =  [[22, 22, 11], [36, 28, 18], [29, 20, 14]]

 

위의 이미지를 보시면 행렬의 곱셈은 규칙성이 있습니다.

이 규칙성을 토대로 코드를 짜면 됩니다.


해결방안

function solution(arr1, arr2) {
    
    // 버전 1
    var answer = [];
    
    for(let i=0; i<arr1.length; i++){
        let matrixArr =[]
        let matrixSum = 0; 
        for(let j=0; j<arr2[0].length; j++){
            matrixSum = arr1[i].reduce((acc, cur, idx)=> acc+ cur*arr2[idx][j], 0);
            matrixArr.push(matrixSum);
        }
        answer.push(matrixArr);
    }
    return answer;
    
    // 버전 2
    return arr1.map((row) => arr2[0].map((a,i) => row.reduce((acc,cur,idx) => acc + cur * arr2[idx][i], 0)))
}

 

이번 해결방안도 두 가지 버전으로 들고 왔습니다.

 

버전 1은 for문과 reduce를 사용해서 행렬의 곱셈을 answer배열에 담아주는 방법이고

버전 2는 map()과 reduce를 사용해서 arr1를 행렬의 곱셈의 값으로 바꾸어주는 방법입니다.

 

버전 1, 버전 2 둘 다 결국은 위에 있는 이미지를 토대로 코드를 짜줍겁니다.

단지 방식만 다를 뿐이고요.

 

 

 

버전 1

// 버전 1
var answer = [];

for(let i=0; i<arr1.length; i++){
    let matrixArr =[]
    let matrixSum = 0; 
    for(let j=0; j<arr2[0].length; j++){
        matrixSum = arr1[i].reduce((acc, cur, idx)=> acc+ cur*arr2[idx][j], 0);
        matrixArr.push(matrixSum);
    }
    answer.push(matrixArr);
}
return answer;

 

첫 번째 for문은 arr1[여기][]를 나타냅니다.

그래서 첫 번째 for문은 arr1.length만큼 반복문으로 돌려주었습니다.

 

두 번째 for문은 arr2[][여기]를 나타냅니다.

그래서 두 번째 for문은 arr2[0].length만큼 반복문으로 돌려주었습니다.

 

여기서 arr2.length만 쓰거나 arr2[i].length로 쓰면 "제출 후 채점하기"에서 전부 다~ 틀립니다.

이유는 두 번째 for문에서는 2차원 배열의 원소의 길이만큼 반복해야 되기 때문입니다.

(즉, [[5, 4, 3], [2, 4, 1], [3, 1, 1]]에서 [5, 4, 3]-> 3번 [2, 4, 1] -> 3번 [3, 1, 1] -> 3번 3번씩만 반복해야 됨)

여기서 arr2[i]를 쓰면 안 되는 이유는

arr1의 배열 길이가 arr[?]의 배열 길이 같지 않을 수도 있기 때문이기도 하고

arr2[0].length, arr2[1].length, arr2[2].length은 다 같기 때문에 [0]이라고 써주어야 됩니다.

(그래서 제가 이거 때문에 삽질했습니다.😔)

 

세 번째는 반복문에서는 arr1[][여기], arr2[여기][]

여기서는 for문으로 적을 수 있지만 reduce로 적었습니다.

reuduce는 배열의 누적된 값을 구할 때 사용하기 편한 것 같아요.

reuduce 에는 누산기 (acc), 현재 값 (cur), 현재 인덱스 (idx)를 인자로 사용할 수 있어서 좋은 것 같아요.

이렇게 3개 다 쓸 경우에는 초기값 필수입니다.

 

reduce안에 acc+ cur*arr2[idx][j] 쓴 이유는 아래 이미지를 보시면 아실 것 같습니다.

 

테스트 2에서 i=0, j=0일 경우에만 테스트해보자면

테스트 2
arr1 = [[2, 3, 2], [4, 2, 4], [3, 1, 4]]
arr2 = [[5, 4, 3], [2, 4, 1], [3, 1, 1]]
return = [[22, 22, 11], [36, 28, 18], [29, 20, 14]]

i = 0; j=0;
arr1[i].reduce((acc, cur, idx)=> acc+ cur*arr2[idx][j], 0);
arr1[0].reduce((acc, cur, idx)=> acc+ cur*arr2[idx][0], 0);

arr1[0][0], idx=0
acc = 0
cur = arr1[0][0] -> 2
arr2[idx][0] -> arr2[0][0] -> 5
=> 0+ 2*5 = 10


arr1[0][1], idx=1
acc = 10
cur = arr1[0][1] -> 3
arr2[idx][0] -> arr2[1][0] -> 2
=> 10+ 3*2 = 10+6 = 16


arr1[0][2], idx=2
acc = 16
cur = arr1[0][2] -> 2
arr2[idx][0] -> arr2[2][0] -> 3
=> 16+ 2*3 = 16+6 = 22

reduce에서 나온 22라는 값은 return[0][0]값과 같습니다.

이런 식으로 반복하게 됩니다.

 

 

 

 

버전 2

// 버전 2
return arr1.map((row) => arr2[0].map((a,i) => row.reduce((acc,cur,idx) => acc + cur * arr2[idx][i], 0)))

버전 2는 arr1를 return으로 바꾸어 주는 코드입니다.

map()은 배열은 새로운 배열을 반환할 때 사용하는 메서드인데요.

map() 메서드를 중복으로 쓰면 1차원 배열에서 2차원 배열로 만들 수 있습니다.

 

첫 번째 map

row는 버전 1로 비유하면 arr[i]와 같습니다.

 

두 번째 map

i는 버전 1에서 j와 같습니다.

두 번째 map에서 arr2[0].map()이라 쓴 이유는 버전 1에서 j<arr2[0].length;랑 쓴 이유랑 같다고 생각하시면 됩니다.

여기서는 index값이 필요한데 이 값을 map메서드에서 두 번째로 들어가서 a, i로 넣어주었습니다.

 

reduce

버전 1에서는 arr1[i].reduce((acc, cur, idx)=> acc+ cur*arr2[idx][j], 0); 이렇게 써주었습니다.

버전 2에는 row.reduce((acc,cur,idx) => acc + cur * arr2[idx][i], 0) 이렇게 써주었습니다.

 

위에서 row는 arr[i]와 같다고 했고, i는 j와 같다고 했습니다.

사실상 버전 1 축약 버전이 버전 2라고 생각하시면 됩니다.


여기까지 프로그래머스 행렬의 곱셈 해결방안에 대해 설명해보았습니다.

 

728x90
반응형
Comments