-
[객체, 배열] 로마자에서 숫자로 바꾸기(수열)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 // 차이를 리턴 }
그런데, 오히려 코드가 길어지기도 했고, 가독성이 좋지 않았다.
마음에 안들었다.
내가 생각한 논리대로라면 로마자 숫자의 길이가 길어질 수록 식이 많이 수정되어야 했다.
그래서 더 좋은 답은 없을지 구글링 해보았고 무릎을 탁 치는 풀이를 찾게 되었다.
// 찾아본 풀이 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 처럼. 이 경우는 같은 값도 올 수 있는데, 특수한 숫자의 경우는 해당 자리수보다 큰 값이 뒤에 나오게 된다. 결국, 어떤 숫자 뒤에 큰 값이 나타나면 그 작은 값을 빼주는 규칙이 적용된 연산인 것이다.
'Algorithm | Data structure > 문제풀이' 카테고리의 다른 글
[프로그래머스] 약수의 개수와 덧셈 (0) 2023.03.17 [프로그래머스] 폰켓몬 (0) 2023.01.17 [객체, 배열] 배열 내 반복 요소 개수 찾기 (0) 2022.11.08 숫자 뒤집기 (0) 2022.11.01 배열 요소의 조합을 찾는 문제 (0) 2022.10.31