로마 숫자 변환 문제
로마 숫자
로마 숫자를 읽는 방법은 두 가지 규칙만 알면 된다.
- 규칙 1. 먼저 각 알파벳이 어떤 숫자를 의미하는지 파악한다.
I = 1,V = 5,X = 10,L = 50,C = 100,D = 500,M = 1000
- 규칙 2. 작은 숫자 하나가 큰 숫자의 왼쪽에 있다면 빼기로 생각해서 읽으면 된다.
IV = 4,IX = 9,XL = 40,XC = 90,CD = 400,CM = 900
다음은 로마 숫자를 올바르지 않게 작성한 경우이다.
IIV는 작은 숫자 두 개가 왼쪽에 왔다. 따라서3 = 5 - 1 - 1이 아니다.3은III로 쓴다.
XCM는 작은 숫자 두 개가 왼쪽에 왔다. 따라서890 = 1000 - 100 - 10이 아니다.890은DCCCXC로 쓴다.D + CCC + XC = 500 + 300 + 90
다음은 올바르게 작성한 예제이다.
- 1 ~ 10:
I,II,III,IV,V,VI,VII,VIII,IX,X - 11 ~ 20:
XI,XII,XIII,XIV,XV,XVI,XVII,XVIII,XIX,XX - 21 ~ 30:
XXI,XXII,XXIII,XXIV,XXV,XXVI,XXVII,XXVIII,XXIX,XXX - 31 ~ 40:
XXXI,XXXII,XXXIII,XXXIV,XXXV,XXXVI,XXXVII,XXXVIII,XXXIX,XL - 41 ~ 50:
XLI,XLII,XLIII,XLIV,XLV,XLVI,XLVII,XLVIII,XLIX,L - 51 ~ 60:
LI,LII,LIII,LIV,LV,LVI,LVII,LVIII,LIX,LX - 61 ~ 70:
LXI,LXII,LXIII,LXIV,LXV,LXVI,LXVII,LXVIII,LXIX,LXX - 71 ~ 80:
LXXI,LXXII,LXXIII,LXXIV,LXXV,LXXVI,LXXVII,LXXVIII,LXXIX,LXXX - 81 ~ 90:
LXXXI,LXXXII,LXXXIII,LXXXIV,LXXXV,LXXXVI,LXXXVII,LXXXVIII,LXXXIX,XC - 91 ~ 100:
XCI,XCII,XCIII,XCIV,XCV,XCVI,XCVII,XCVIII,XCIX,C
아라비아 숫자를 로마 숫자로 변환
아라비아 숫자를 로마 숫자로 변환하는 코드를 작성하는 건 꽤 간단하면서도 재미있는 작업이다.
단순한 방법
생각할 수 있는 가장 단순한 방법은 다음과 같을 것이다. 언어는 Javascript를 사용했다.
// 값을 정의한다
const mapping = [
{ arabic: 1000, roman: 'M' },
{ arabic: 900, roman: 'CM' },
{ arabic: 500, roman: 'D' },
{ arabic: 400, roman: 'CD' },
{ arabic: 100, roman: 'C' },
{ arabic: 90, roman: 'XC' },
{ arabic: 50, roman: 'L' },
{ arabic: 40, roman: 'XL' },
{ arabic: 10, roman: 'X' },
{ arabic: 9, roman: 'IX' },
{ arabic: 5, roman: 'V' },
{ arabic: 4, roman: 'IV' },
{ arabic: 1, roman: 'I' },
];
// 아라비아 숫자를 입력받아 로마 숫자 문자열을 리턴한다
const arabic_to_roman = (arabic) => {
let result = '';
let i = 0;
while (arabic > 0) {
if (arabic >= mapping[i].arabic) {
result = result.concat(mapping[i].roman)
arabic -= mapping[i].arabic;
} else {
i++;
}
}
return result;
};
로직은 단순하다. 주어진 아라비아 숫자에서 뺄 수 있는 가장 큰 로마 숫자를 뺄셈해가며 문자열을 완성하는 방법이다.
만약 루프를 좀 덜 돌게 하고 싶다면 이렇게 할 수도 있겠다.
// 아라비아 숫자를 입력받아 로마 숫자 문자열을 리턴한다
const arabic_to_roman = (arabic) => {
let result = '';
let i = 0;
while (arabic > 0) {
if (arabic >= mapping[i].arabic) {
const count = Math.floor(arabic / mapping[i].arabic);
result = result.concat(mapping[i].roman.repeat(count));
arabic = arabic % mapping[i].arabic;
} else {
i++;
}
}
return result;
};
재귀로 표현하는 것도 재미있는 방법일 것이다.
// 아라비아 숫자를 입력받아 로마 숫자 문자열을 리턴한다
const arabic_to_roman = (arabic, level) => {
if (arabic <= 0) {
return '';
}
if (level == undefined) {
level = 0;
}
const count = Math.floor(arabic / mapping[level].arabic);
const prefix = mapping[level].roman.repeat(count);
const postfix = arabic_to_roman(arabic % mapping[level].arabic, level + 1);
return prefix + postfix
};
테스트 코드는 굳이 테스트 프레임워크를 사용하지 않고도 다음과 같이 단순하게 작성할 수 있다.
const check_list = [
{ arabic: 1954, roman: 'MCMLIV' },
{ arabic: 1776, roman: 'MDCCLXXVI' },
{ arabic: 1066, roman: 'MLXVI' },
{ arabic: 207, roman: 'CCVII' },
{ arabic: 246, roman: 'CCXLVI' },
{ arabic: 39, roman: 'XXXIX' },
{ arabic: 10, roman: 'X' },
{ arabic: 7, roman: 'VII' },
{ arabic: 4, roman: 'IV' },
{ arabic: 3, roman: 'III' },
{ arabic: 2, roman: 'II' },
{ arabic: 1, roman: 'I' },
];
check_list.forEach((check) => {
const result = arabic_to_roman(check.arabic);
const correct = (result === check.roman) ? '맞음' : '틀림';
console.log(`결과: ${correct}, ${check.arabic} = ${check.roman}`);
});
실행 결과는 다음과 같다.
결과: 맞음, 1954 = MCMLIV
결과: 맞음, 1776 = MDCCLXXVI
결과: 맞음, 1066 = MLXVI
결과: 맞음, 207 = CCVII
결과: 맞음, 246 = CCXLVI
결과: 맞음, 39 = XXXIX
결과: 맞음, 10 = X
결과: 맞음, 7 = VII
결과: 맞음, 4 = IV
결과: 맞음, 3 = III
결과: 맞음, 2 = II
결과: 맞음, 1 = I
파이프라인을 구성하는 방법
나는 이 문제를 예전에 다녔던 회사에서 아샬님을 통해 알게 됐는데, 그 때 나는 파이프라인 방식을 아주 좋아했기 때문에 다음과 같이 풀었다.
몇 년이 지났지만 나는 이 방법이 아직도 꽤 마음에 든다.
const print = (str) => process.stdout.write(str);
const chain = (a, b) => b(a);
function calc(arabic, roman_sign, input_number) {
const count = Math.floor(input_number / arabic);
print(roman_sign.repeat(count));
return input_number % arabic;
}
const roman = (arabic, roman_sign) => calc.bind(null, arabic, roman_sign);
const mapping = [
(num) => { print(`${num} : `); return num; },
roman(1000, "M"),
roman(900, "CM"),
roman(500, "D"),
roman(400, "CD"),
roman(100, "C"),
roman(90, "XC"),
roman(50, "L"),
roman(40, "XL"),
roman(10, "X"),
roman(5, "V"),
roman(4, "IV"),
roman(1, "I"),
() => console.log(),
];
const printRoman = (num) => mapping.reduce(chain, num);
printRoman(1954);
printRoman(1776);
printRoman(1066);
printRoman(207);
printRoman(246);
printRoman(39);
printRoman(10);
printRoman(7);
printRoman(4);
printRoman(3);
printRoman(2);
printRoman(1);
로마 숫자를 아라비아 숫자로 변환
정규식을 쓰는 방법
Codewars의 Roman Numerals Decoder 문제의 해법 중 하나를 보고 배운 방법이다.
아래의 코드는 이 방법을 응용해 내가 Clojure로 작성한 것으로 let을 사용하지 않고 스레딩 매크로(->>)를 사용한다는 점이 다르다.
(def roman-number {"M" 1000
"CM" 900
"D" 500
"CD" 400
"C" 100
"XC" 90
"L" 50
"XL" 40
"X" 10
"IX" 9
"V" 5
"IV" 4
"I" 1})
(defn translate-roman-numerals [roman-number-string]
(->> (re-seq #"CM|CD|XC|XL|IX|IV|[MDCLXVI]" roman-number-string)
(map roman-number)
(apply +)))
Clojure의 간결함이 눈에 띈다.