Playing with digits<6 kyu>

jjanmo·2019년 12월 25일
0

Codewars에서 뒹굴기

목록 보기
2/32

🚀 문제를 풀어나갈 때 생각의 흐름을 정리합니다. 또한 새로운 풀이에 대한 코드를 분석하고 모르는 부분에 대해서 정리합니다. 생각이 다른 부분에 대한 피드백은 언제나 환영합니다. 틀린 내용에 대한 피드백 또한 항상 감사합니다.

문제링크

문제

Some numbers have funny properties. For example:

89 --> 8¹ + 9² = 89 1
695 --> 6² + 9³ + 5⁴= 1390 = 695
2
46288 --> 4³ + 6⁴+ 2⁵ + 8⁶ + 8⁷ = 2360688 = 46288 * 51

Given a positive integer n written as abcd... (a, b, c, d... being digits) and a positive integer p.
we want to find a positive integer k, if it exists, such as the sum of the digits of n taken to the successive powers of p is equal to k * n.
In other words:

Is there an integer k such as : (a ^ p + b ^ (p+1) + c ^(p+2) + d ^ (p+3) + ...) = n * k

If it is the case we will return k, if not return -1.
Note: n and p will always be given as strictly positive integers.

👉 문제해석
문제에서 간단하게 정리해주었다. ➀(a ^ p + b ^ (p+1) + c ^(p+2) + d ^ (p+3) + ...)n * k, ➀과 ➁을 같게 만들 수 있는지를 물어보는 문제였다. k값에 따라서 리턴하는 값이 달라진다. k가 존재하면 k값을, 그렇지않으면 -1을 리턴하게 된다.

문제접근

  • 첫번째 생각 : 연산자 / 와 %를 이용하면 해결할 수 있지 않을까?

    function digPow(n, p) {
      let sum = 0, cnt = 0;
      while (n > 0) {
        sum += Math.pow(n % 10, p + cnt);
        n = (n / 10).toFixed(0);
        cnt++;
      }
      return sum / n === 1 ? 1 : -1; 
    }

    결론은 이렇게 풀면 안된다. 문제를 접근할 때 잘못 생각한 것들이 있다.

    1. 문제에서는 주어진 숫자(n)의 앞에서부터 한자리씩 접근해서 계산이 이루어진다. 하지만 난 %연산자를 사용했기 때문에 주어진 숫자의 뒤에서부터 접근하게 된다.
    2. 자바스크립트는 / 연산을 할 때, 다른 언어와는 다르게 몫만을 보여주지 않는다. 예를 들어 7 / 3을 하면 2.3333333333333335 이런 값을 보여준다. 그렇기 때문에 sum / n의 값이 양의 정수인지 아닌지를 어떻게 판별할 것인지에 대해서도 생각을 해주어야 할 것 같다.
      
  • 두번째 생각 : 주어진 숫자를 분리해서 한자리씩 접근하도록 해야겠다. 그렇게 할려면 split() 메소드를 사용해서 하나씩 분리해서 배열로 접근을 해야겠는걸?!

    function digPow(n, p){
      let sum = 0;  
      n.toString().split('').forEach((v,i)=>
                                     sum += Math.pow(Number(v),p+i));
      return sum === n ? 1 : (Number.isInteger(sum/n) ? sum/n : -1);
    }

    최종 제출 코드이다.

    1. 숫자를 배열로 만든 후 생각했던대로 split() 메소드를 사용해서 숫자의 한자리씩 들어있는 배열을 만들었다. 그렇게 해서 forEach()를 이용해서 첫번째 자리부터 접근할 수 있었다.
    2. 위에서 언급했듯이 양의 정수인지 아닌지를 구분하는 방법이 무엇일까에 대해서 고민하다가, isArray()라든지 isNaN()같은 메소드도 있다는 걸 알고 있었기 때문에 아마도 정수인지 아닌지를 구분하는 메소드도 존재하지 않을까하는 생각이 들어 찾아봤다. 역시나 isInteger()라는 메소드가 있었다. Number.isInteger()는 주어진 값이 정수인지 아닌지를 판별하는 메소드로 boolean값을 리턴한다. 이를 이용해서 리턴값을 결정지었다. 이 때 생각하지 못했던 것은 리턴값인 1도 나눠떨어졌을 때 나온 몫이라는 것이다. 삼항 연산자의 분류 기준을 1인 경우와 그렇지 않은 경우로 잡을 필요가 없었다는 것을 알게 되었다.(이 부분은 Best Solution 쪽에서 더 설명한다)

Best solution

  • 첫번째 풀이
function digPow(n, p) {
	var x = String(n).split("").reduce((s, d, i) => s + Math.pow(d, p + i), 0);
  	return x % n ? -1 : x / n;
}
  • 두번째 풀이
function digPow(n, p){
	var ans = (''+n).split('')
    				.map(function(d,i){return Math.pow(+d,i+p) })
    				.reduce(function(s,v){return s+v}) / n;
    return ans%1 ? -1 : ans    
}

2개의 풀이에서 주목해야 할 부분이 2가지가 있다. 첫번째는 reduce()메소드이고 두번째는 return할 때 나와 다르게 접근한 점이다. 이 2가지에 대해서 알아보자.

주목할 부분

  1. reduce() 메소드
    • 주어진 배열의 각 요소에 대해서 주어진 reducer 함수를 실행하고, 그에 대한 하나의 결과값을 반환한다.
    • syntax
      - array.reduce(callback, [initial value]); 콜백 함수가 reducer 함수를 의미
      - 콜백 함수는 4개의 인자를 갖는다. accumulator(누적값), currentValue(현재 원소의 값), currentIndex(현재 배열의 인덱스값), array(배열)
      - initial value의 유무에 따라서 콜백 함수가 최초 호출 될 때의 인자들의 값이 달라진다.
initial value 제공accumulatorcurrentValuecurrentIndex
Oinitial valuearray[0]0
Xarray[0]array[1]1

initial value가 제공 될 때, array의 0번 인덱스부터 시작한다.
initial value가 제공 되지 않을때, array의 1번 인덱스부터 시작하고, 0번 인덱스는 건너뛴다.

var sum = [2, 3, 4, 5].reduce(function (accumulator, currentValue, curruntIndex) {
  console.log(accumulator, currentValue, curruntIndex);
  return accumulator + currentValue;
}, 2);
console.log('sum', sum);
/*
output
2 2 0
4 3 1
7 4 2
11 5 3
sum 16
*/
 var sum1 = [2, 3, 4, 5].reduce(function (accumulator, currentValue, curruntIndex) {
   console.log(accumulator, currentValue, curruntIndex);
   return accumulator + currentValue;
 });
 console.log('sum1', sum1);
 /*
 output
 2 3 1
 5 4 2
 9 5 3
 sum1 14
 */

각각의 실제 코드에서 보면 알 수 있듯이 같은 배열을 사용하더라도 initial value값이 있고 없고에 따라서 결과값이 달라짐을 알 수 있다. 하지만 만약에 initial value가 0이라면 누적값에 변화가 없어서 결과값이 initial값의 유무와 관계 없이 같아지게 된다. Best solution에서 살펴보면, 첫번째 풀이에서는 0을 주었고, 두번째 풀이에서는 initial value값 자체를 주지 않았다. 첫번째 풀이는 배열을 모두 반복하게 되는 것이고, 두번째 풀이는 첫번째 수(index 0)는 반복하지 않고(누적합에 이미 array[0]으로 설정됨), 두번째 수(index 1)부터 반복하겠다는 의미가 되는 것이다. 결국 결과값은 두 풀이 모두 같다.

여기서는 단순하게 덧셈을 하기위해서 reduce() 메소드를 사용하였지만, 여러 예제들을 살펴보면 단순 누적합을 구하는 것 말고도 객체내에서 인스턴스의 개수를 세기, 속성으로 객체 분류하기 등등으로 다양하게 사용할 수 있음을 알게 되었다. 이 부분은 추후에 더 공부할 테마가 될 것 같다.

  1. 자바스크립트에서 false에 해당하는 값이 무엇인가
  • undefined, null, NaN, 0,-0, ''(빈문자열) : 자바스크립트에서는 false값에 해당나의 풀이와 Best Solution의 차이에 대해서 생각해보겠다. 나의 경우 삼항연산자의 분류기준을 1과 1이 아닌 것을 구분하고 나눈 몫이 정수인지 아닌지를 통해서 구분하였다. 즉, 나는 몫을 기준으로 접근하였다. 그런데 Best Solution은 나머지를 분류기준으로 선택하였다. 여기서 자바스크립트에서 false값이 무엇인지를 알아야 이를 적용시킬 수 있다. 첫번째 풀이의 코드를 보자. x % n ? -1 : x / n 나머지가 0인 것, x % n 에서 0 이 나오면 이는 false에 해당하게 되고 그러면 x / n을 실행하게 된다. 이미 나머지가 0이였기 때문에 나눈 몫은 무조건 정수 값을 갖게 되며, 그 자체가 리턴값이 된다. 그 외의 경우는 0이 아니기 때문에 true로 간주되고 -1값이 리턴된다.

결론

이 문제에 대해서 설명하기 위해서 몰랐던 부분과 잊어버린 부분 그리고 알고 있는데도 활용하지 못했던 부분에 대해서 점검할 수 있는 좋은 기회였다. 지식이라는 것은 많이 알고 있으면 좋은 것은 맞다. 하지만 그보다 더 좋은 것은 아는 것들에 대해서만이라도 정확하게 활용할 수 있는 것이 더 좋은 것이라고 생각한다. 그럼에도 불구하고 새로운 것들에 대해 지속적으로 공부하다보면, 그 새로운 것들이 예전의 것을 되돌아보게 만들고 예전의 것을 더욱 견고하게 만들어가는 과정이라고 생각한다.

참고

MDN Array.prototype.reduce()
MDN Boolean

      
profile
눈길을 걸어갈 때 어지럽게 걷지 말기를.

0개의 댓글