[TID][Lv 0] 코딩 테스트 연습_1

MoonEn·2022년 12월 11일
0

Today I Learned

목록 보기
2/8
post-thumbnail

해당 스토리는 코드테스트 공부 중에 새롭게 배운 내용을 기록하고 자기 반성을 위해 기록해 둔다.

연습 문제 - 분수 더하기

첫 번째 분수의 분자와 분모를 뜻하는 denum1, num1, 두 번째 분수의 분자와 분모를 뜻하는 denum2, num2가 매개변수로 주어집니다. 두 분수를 더한 값을 기약 분수로 나타냈을 때 분자와 분모를 순서대로 담은 배열을 return 하도록 solution 함수를 완성해보세요.
I

문제 풀이

1. 통분하여 더한 후, 기약분수 계산하기

분모가 다른 분수를 더하기 위해서는 기본적으로 통분*을 한다. 통분을 하기 위해서는 두 분모의 최소공배수를 알아야 하고, 각각의 분모와 분자에 분모들이 최소공배수가 되는 값을 동일하게 곱해야 한다. 즉, 아래와 같은 방식이다.

기약 분수는 분자와 분모를 약분**하여 간단히 나타내는 것을 의미한다. 기약 분수가 매개변수로 주어진다면 통분하여 더하는 것 만으로 문제가 해결된다. 하지만 기약 분수가 아닌 분수가 매개 변수로 주어진다면, 더한 후에 다시 최대공약수로 나눠야 한다.

통분하기 위해 최소공배수를 계산하고, 기약분수를 만들기 위해 최대공약수를 계산하는 것은 매우 귀찮은 일이다. 이것보다 쉬운 방법이 있었다.

2. 간단하게 분수를 더한 후, 기약분수 계산하기

분수1(분자1, 분모1)과 분수2(분자2, 분모2)가 있다. ((분자1분모2)+(분자2분모1))/분모1*분모2를 하면 된다.

분수는 더했다. 기약분수는 분자와 분모의 최대공약수를 찾아서 나누기만 하면 된다. 38과 24의 최대공약수는 2이다.

자, 이제 이걸 코드로 만들면 된다.

나의 코드

먼저 통분하여 더해보자.

function solution(denum1, num1, denum2, num2) {
    let answer = [];
    let a = denum1*num2;
    let b = denum2*num1;
    let denominator = num1*num2;
    let numerater = a+b;

변수 a에는 분자1과 분모2를 곱한 값을 할당한다.
변수 b에는 분자2와 분모1을 곱한 값을 할당한다.
변수 denominator에는 분모1과 분모2를 곱한 값을 할당한다.(분모가 됨)
변후 numerater에는 a와 b를 더한 값을 할당한다.(분자가 됨)

이제 최대공약수를 구해보자!

위와 같은 방식(소인수분해)으로 만들기엔 복잡할 것 같아서 다른 방식을 찾아보았다.
유클리드 호제법이라는 것이 있다. 증명에 관해 궁금하시다면 아래의 링크를 참조하길 바란다.

유클리드 호제법

간단히 설명하면, 두 수 x, y가 있을 때(x>y) x를 y로 나눈 나머지로 다시 y를 나누고 나머지를 다시 나머지에 나누고... 결국 나머지가 0이되는 수가 최대공약수라는 것이다.

이를 구현하기 위해 아래와 같이 코드를 만들었다.

 const gcd = (n, d) =>{
        if(n>=d){
           return n%d === 0 ? d : (gcd(d, n%d));
        } else if(n<d){
           return d%n === 0 ? n : (gcd(n, d%n));
        }          
    }

분자와 분모를 아래와 같이 파라미터 n과 d로 전달 받는다. 나누는 숫자가 나눠지는 숫자보다 작아야 하므로, if문으로 분기하여 작은 숫자로 나눌 수 있도록 하였다. 그리고 나눈 다음 나머지가 0이 되면 나눈 숫자가 반환되도록 하였고, 나머지가 0이 아니면, 다시 함수를 불러와서 나눈 숫자를 나머지로 다시 나눌 수 있도록 만들었다.

이렇게 구한 최대공약수로 분자와 분모를 나눈 뒤, 순서대로 배열에 집어넣으면 성공!

과연... 테스트 결과는?? 다행히 통과!

전체코드

function solution(denum1, num1, denum2, num2) {
    let answer = [];
    let a = denum1*num2;
    let b = denum2*num1;
    let denominator = num1*num2;
    let numerater = a+b;
    const gcd = (n, d) =>{
        if(n>=d){
           return n%d === 0 ? d : (gcd(d, n%d));
        } else if(n<d){
           return d%n === 0 ? n : (gcd(n, d%n));
        } 
    }    
    answer.push(numerater/gcd(numerater,denominator))
    answer.push(denominator/gcd(numerater,denominator))
    return answer;
}

반성

간단한 코드 임에도 전체적으로 길고, 변수의 사용도 많다. 문제 풀이에만 급급한 모습니다.
변수 a와 b를 따로 만들고 그걸 다시 새로운 변수에 넣었다. 그렇게 하지 않고 아래처럼 1개의 변수에 넣어도 되었지 않았을까?

let a = denum1*num2;
let b = denum2*num1;

let numerater = denum1*num2+denum2*num1;

그리고 최소공배수를 구하는 함수에서 파라미터 크기를 구분해서 작성하였는데, 생각을 해보면 작은 수를 큰 수로 나누면 작은 수가 나머지가 된다.
3/5의 나머지는 3

결국 나눈 수를 나머지로 나누게 된다. 크기를 구분할 필요가 없다는 것이다.

const gcd = (n, d) =>{
	return n%d === 0 ? d : (gcd(d, n%d));
}

마지막으로 함수 안에 있던 함수를 밖으로 빼고, 둘 다 Arrow Function으로 만들어 보자.

const gcd = (n, d) =>{
    return n%d === 0 ? d : (gcd(d, n%d));
}    
const solution = (denum1, num1, denum2, num2) => {
    let answer = [];
    let denominator = num1*num2;
    let numerater = denum1*num2+denum2*num1;
    answer.push(numerater/gcd(numerater,denominator))
    answer.push(denominator/gcd(numerater,denominator))
    return answer;
}

완성!

마무리

글로 써보니 뭔가 거창한 걸 한 것 같다. 단순한 분수 더하기라고 생각할 수 있지만, 이것을 해결하기 위해 고민한 나에게는 꽤나 만족스러운 기록이다. 나중에 다시 이 포스트를 보고 이 땐 이랬지라고 생각하며, 수정 포스트를 올릴 날도 있을 것이다. 그 때를 기약하며 나는 다시 공부하러 가겠다.

혹시 코멘트 하실 부분이 있다면 언제든 댓글로 참견 바란다.



*둘 이상의 분수의 분모를 같게 만드는 것 - 출처: 네이버 초등수학 개념사전
** 분수의 분모와 분자를 그들의 공약수로 나누는 것 - 출처: 네이버 초등수학 개념사전

profile
개발자를 꿈꾸는 직장인

0개의 댓글