ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [객체, 배열] 로마자에서 숫자로 바꾸기(수열)
    Algorithm | Data structure/문제풀이 2022. 11. 8. 09:45

    Q.  로마자에서 숫자로 바꾸기

     

    1~3999 사이의 로마자 s를 인자로 주면 그에 해당하는 숫자를 반환해주세요. 로마 숫자를 숫자로 표기하면 다음과 같습니다.

    Symbol       Value
    I             1
    V             5
    X             10
    L             50
    C             100
    D             500
    M             1000

    로마자를 숫자로 읽는 방법은 로마자를 왼쪽부터 차례대로 더하면 됩니다. III = 3 XII = 12 XXVII = 27 입니다.

    그런데 4를 표현할 때는 IIII가 아니라 IV 입니다. 뒤의 숫자에서 앞의 숫자를 빼주면 됩니다. 9는 IX입니다.

    I는 V와 X앞에 와서 4, 9 X는 L, C앞에 와서 40, 90 C는 D, M앞에 와서 400, 900입니다.

     

    Answer

    - 우선 주어진 로마자를 split 메소드를 이용해 배열로 spread 하면 되겠다고 생각했다.

    - 그 후에 모든 로마자가 포함된 개수대로 숫자를 더해주면 되겠다고 생각했다.

    - 그런데 문제는 특수한 규칙을 가진 로마자 숫자의 존재였고 찾았던 규칙은, 그 로마자를 분해하여 더하는 것과 원래 숫자의 크기 차이가 2, 20, 200씩 차이가 난다는 것을 알게 되었다. (ex. I + V = 6, IV = 4 => 차이는 2 / X + L = 60, XL = 40 => 차이는 20 ...)

    - 그래서 모두 분해하여 더한 숫자에 특수한 숫자들의 개수를 찾아 차이만큼 빼주었다.

    - 경우의 수를 찾는 문제로 바꾸어 푼 것이다.

    // 첫 번째 답안
    function romanToNum(s) {
      
      const arr = s.split(‘’);
      const i = arr.filter(item => item === ‘I’).length
      const v = arr.filter(item => item === ‘V’).length*5
      const x = arr.filter(item => item === ‘X’).length*10
      const l = arr.filter(item => item === ‘L’).length*50
      const c = arr.filter(item => item === ‘C’).length*100
      const d = arr.filter(item => item === ‘D’).length*500
      const m = arr.filter(item => item === ‘M’).length*1000
      const iv = (s.split(‘IV’).length - 1)*2
      const xl = (s.split(‘XL’).length - 1)*20
      const xc = (s.split(‘XC’).length - 1)*20
      const cd = (s.split(‘CD’).length - 1)*200
      const cm = (s.split(‘CM’).length - 1)*200
      result = i+v+x+l+c+d+m-iv-xl-xc-cd-cm
      return result;
     }

     

    문제를 풀고나니 식들이 simple해서 좋긴한데, 반복이 많아서 줄이고 싶었다. 그런데, 각 로마자와 그 값은 꼭 객체의 프로퍼티의 key와 value 같은 모습이었다.

    그래서 객체로 만들었다. 단, IV, XL 같은 값들은 차이값으로 value 를 지정해주었다.

    그리고 그 객체를 순회하며 연산하기 위해 for ~ in 문을 활용했다.

     

    // 두 번째 답안
    function romanToNum(s) {
      const arr = [...s]
      const numObj = {
        I: 1,
        V: 5,
        X: 10,
        L: 50,
        C: 100,
        D: 500,
        M: 1000
      }
      const specialNumObj = {
        IV : 2,
        XL : 20,
        XC : 20,
        CD : 200,
        CM : 200
      }
      // 로마자 숫자 합 구하기
      let numArr = [];
      let num ;
      for (let key in numObj) {
        arr.filter(item => {
          if(item == key){
          numArr.push(numObj[key])
        }})
      }
      const numSum = numArr.reduce((start, next) => start + next)
      
      // 독특한 로마자 숫자 합 구하기
      let specialNumArr = [];
      for (let key in specialNumObj) {
        specialNumArr.push( (s.split([key]).length - 1) * specialNumObj[key] )
      }
      const specialNumSum = specialNumArr.reduce((start, next) => start + next)
      
      
      return numSum - specialNumSum // 차이를 리턴
    }

     

    그런데, 오히려 코드가 길어지기도 했고, 가독성이 좋지 않았다.

     

    마음에 안들었다.

     

    내가 생각한 논리대로라면 로마자 숫자의 길이가 길어질 수록 식이 많이 수정되어야 했다.

    그래서 더 좋은 답은 없을지 구글링 해보았고 무릎을 탁 치는 풀이를 찾게 되었다.

     

    출처 : https://velog.io/@eunjin/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%A1%9C%EB%A7%88%EC%9E%90%EC%97%90%EC%84%9C-%EC%88%AB%EC%9E%90%EB%A1%9C-%EB%B0%94%EA%BE%B8%EA%B8%B0

     

    // 찾아본 풀이
    function romanToNum(s) {
      const romeNum = {
        'I': 1,
        'V': 5,
        'X': 10,
        'L': 50,
        'C': 100,
        'D': 500,
        'M': 1000 
      }
      let result = 0
      const romeArray = s.split('')
      const numArray = romeArray.map(rome => romeNum[rome])
      
      for (i=0; i<numArray.length; i++) {
        if (numArray[i] < numArray[i+1] ) {
          result -= numArray[i]
        } else {
          result += numArray[i]
        }
      }
      
      return result
    }
    
    console.log(romanToNum('III')) // 3
    console.log(romanToNum('XII')) // 12
    console.log(romanToNum('XXVII')) // 27
    console.log(romanToNum('MCMXCIV')) // 1994

     

    알고리즘은, 

    경우의 수로 접근하기보다, 수열로 접근하는 것이 옳은 것 같다.

    로마 숫자가 적히는 규칙은 어쨌든, 그 값이 큰 수가 앞에 오게 된다.

    그런데 독특한 숫자의 규칙은 작은 수가 앞에 오고 큰 수가 뒤에 오게 된다.

    그 때마다 작은 수의 값을 빼주고 나머지 숫자를 더해주면, 결과적으로는 우리가 얻고자하는 숫자를 얻게 된다.

     

    배운 자료변환 방식은,

    자료를 변환 해줄 Index로 활용할 객체를 (index : value)로 만들고

    index가 포함된 배열에 map 메소드를 활용해 해당 배열을 value 배열로 변환해주는 것이다.

    그러면 위와 같은 알고리즘에 의해 연산이 가능해진다.

    console.log(romanToNum('III')) // 3
    console.log(romanToNum('XII')) // 12
    console.log(romanToNum('XXVII')) // 27
    console.log(romanToNum('MCMXCIV')) // 1994
    
    
    III => 1 + 1 + 1
    XII => 10 + 1 + 1
    XXVII => 10 + 10 + 5 + 1 + 1
    MCMXCIV => M + (M - C) + (C - X) + (V - I) = 1000 + (1000 - 100) + (100 - 10) + (5 - 1)
    으로 연산해야 한다고 생각하지만,
    => M - C + M - X + C - I + V  = 1000 - 100 + 1000 - 10 + 100 - 1 + 5와 결국 같은 값이다.
    상식적으로 자리수 뒤에는 해당 자리수보다 작은 값이 온다. 11 = 10 + 1 처럼.
    이 경우는 같은 값도 올 수 있는데, 특수한 숫자의 경우는 해당 자리수보다 큰 값이 뒤에 나오게 된다.
    결국, 어떤 숫자 뒤에 큰 값이 나타나면 그 작은 값을 빼주는 규칙이 적용된 연산인 것이다.

    댓글