/* package whatever; // don't place package name! */
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Optimizations:
* - Creates a map to get each roman numeral decimal value with O(1) complexity
* - All Regex compiled only once
* - Minimum code run each time after initialization
*/
/* Name of the class has to be "Main" only if the class is public. */
class RomanToDecimalConverter {
public static void main
(String[] args
) { printRomanToDecimal("XCIX");
printRomanToDecimal("MMMCMXCIX");
//error cases
printRomanToDecimal("IIX");
printRomanToDecimal("IIXX");
printRomanToDecimal("ABC");
printRomanToDecimal("IVX");
}
private static void printRomanToDecimal
(String s
) { int decimal = convert(s);
String output
= decimal
!= -1 ? ""+decimal
: "Invalid roman numerals"; System.
out.
println(s
+ ": " + output
); }
private static final Map
<String, Integer
> ROMAN_NUMERALS_TO_DECIMAL_MAP
;
static {
//init the correspondence map <RomanNumeral, DecimalValue> when the class is loaded
ROMAN_NUMERALS_TO_DECIMAL_MAP = new HashMap<>();
String[] romanNumerals
= {"M",
"CM",
"D",
"CD",
"C",
"XC",
"L",
"XL",
"X",
"IX",
"V",
"IV",
"I"}; int[] decimalValues = {1000,900,500,400,100,90,50,40,10,9,5,4,1};
//let's map each roman numeral to its decimal value
for (int i = 0; i < decimalValues.length; i++) {
ROMAN_NUMERALS_TO_DECIMAL_MAP.put(romanNumerals[i], decimalValues[i]);
}
}
//checks roman numerals format (max number is 3999 = MMMCMXCIX)
//@see http://w...content-available-to-author-only...e.ro/reguli_scriere_cifre_numere_romane.php?lang=en
private static final Pattern CHECK_ROMAN_NUMERALS_FORMAT_REGEX =
Pattern.compile("^(M{0,3})(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$");
//we need this compiled regex to match each roman numeral one by one
//from left to right from the highest to the lowest decimal value
private static final Pattern FIND_ROMAN_NUMERALS_ONE_BY_ONE_REGEX =
Pattern.compile("M|CM|D|CD|C|XC|L|XL|X|IX|V|IV|I");
private static boolean containsValidRomanNumerals
(String s
) { return !(s == null || s.isEmpty() || !CHECK_ROMAN_NUMERALS_FORMAT_REGEX.matcher(s).matches());
}
/**
* Converts roman numerals to their decimal value
* @param s String containing roman numerals
* @return decimal value corresponding to the roman numerals input
*/
public static int convert
(String s
) { if (!containsValidRomanNumerals(s)) return -1;
final Matcher matcher = FIND_ROMAN_NUMERALS_ONE_BY_ONE_REGEX.matcher(s);
int sum = 0;
//every time we find a roman numeral...
while (matcher.find()) {
//...let's add its decimal value to the sum
sum += ROMAN_NUMERALS_TO_DECIMAL_MAP.get(matcher.group(0));
}
return sum;
}
}
LyogcGFja2FnZSB3aGF0ZXZlcjsgLy8gZG9uJ3QgcGxhY2UgcGFja2FnZSBuYW1lISAqLwppbXBvcnQgamF2YS51dGlsLkhhc2hNYXA7CmltcG9ydCBqYXZhLnV0aWwuTWFwOwppbXBvcnQgamF2YS51dGlsLnJlZ2V4Lk1hdGNoZXI7CmltcG9ydCBqYXZhLnV0aWwucmVnZXguUGF0dGVybjsKCi8qKgogKiBPcHRpbWl6YXRpb25zOgogKiAtIENyZWF0ZXMgYSBtYXAgdG8gZ2V0IGVhY2ggcm9tYW4gbnVtZXJhbCBkZWNpbWFsIHZhbHVlIHdpdGggTygxKSBjb21wbGV4aXR5CiAqIC0gQWxsIFJlZ2V4IGNvbXBpbGVkIG9ubHkgb25jZQogKiAtIE1pbmltdW0gY29kZSBydW4gZWFjaCB0aW1lIGFmdGVyIGluaXRpYWxpemF0aW9uCiAqLwoKLyogTmFtZSBvZiB0aGUgY2xhc3MgaGFzIHRvIGJlICJNYWluIiBvbmx5IGlmIHRoZSBjbGFzcyBpcyBwdWJsaWMuICovCmNsYXNzIFJvbWFuVG9EZWNpbWFsQ29udmVydGVyIHsKCXB1YmxpYyBzdGF0aWMgdm9pZCBtYWluIChTdHJpbmdbXSBhcmdzKSB7CgkJcHJpbnRSb21hblRvRGVjaW1hbCgiWENJWCIpOwogICAgICAgIHByaW50Um9tYW5Ub0RlY2ltYWwoIk1NTUNNWENJWCIpOwoKICAgICAgICAvL2Vycm9yIGNhc2VzCiAgICAgICAgcHJpbnRSb21hblRvRGVjaW1hbCgiSUlYIik7CiAgICAgICAgcHJpbnRSb21hblRvRGVjaW1hbCgiSUlYWCIpOwogICAgICAgIHByaW50Um9tYW5Ub0RlY2ltYWwoIkFCQyIpOwogICAgICAgIHByaW50Um9tYW5Ub0RlY2ltYWwoIklWWCIpOwoJfQoJCglwcml2YXRlIHN0YXRpYyB2b2lkIHByaW50Um9tYW5Ub0RlY2ltYWwoU3RyaW5nIHMpIHsKCQlpbnQgZGVjaW1hbCA9IGNvbnZlcnQocyk7CgkJU3RyaW5nIG91dHB1dCA9IGRlY2ltYWwgIT0gLTEgPyAiIitkZWNpbWFsIDogIkludmFsaWQgcm9tYW4gbnVtZXJhbHMiOwoJCVN5c3RlbS5vdXQucHJpbnRsbihzICsgIjogIiArIG91dHB1dCk7Cgl9CgkKICAgIHByaXZhdGUgc3RhdGljIGZpbmFsIE1hcDxTdHJpbmcsIEludGVnZXI+IFJPTUFOX05VTUVSQUxTX1RPX0RFQ0lNQUxfTUFQOwoKICAgIHN0YXRpYyB7CiAgICAgICAgLy9pbml0IHRoZSBjb3JyZXNwb25kZW5jZSBtYXAgPFJvbWFuTnVtZXJhbCwgRGVjaW1hbFZhbHVlPiB3aGVuIHRoZSBjbGFzcyBpcyBsb2FkZWQKICAgICAgICBST01BTl9OVU1FUkFMU19UT19ERUNJTUFMX01BUCA9IG5ldyBIYXNoTWFwPD4oKTsKCiAgICAgICAgU3RyaW5nW10gcm9tYW5OdW1lcmFscyA9IHsiTSIsIkNNIiwiRCIsIkNEIiwiQyIsIlhDIiwiTCIsIlhMIiwiWCIsIklYIiwiViIsIklWIiwiSSJ9OwogICAgICAgIGludFtdIGRlY2ltYWxWYWx1ZXMgPSB7MTAwMCw5MDAsNTAwLDQwMCwxMDAsOTAsNTAsNDAsMTAsOSw1LDQsMX07CgogICAgICAgIC8vbGV0J3MgbWFwIGVhY2ggcm9tYW4gbnVtZXJhbCB0byBpdHMgZGVjaW1hbCB2YWx1ZQogICAgICAgIGZvciAoaW50IGkgPSAwOyBpIDwgZGVjaW1hbFZhbHVlcy5sZW5ndGg7IGkrKykgewogICAgICAgICAgICBST01BTl9OVU1FUkFMU19UT19ERUNJTUFMX01BUC5wdXQocm9tYW5OdW1lcmFsc1tpXSwgZGVjaW1hbFZhbHVlc1tpXSk7CiAgICAgICAgfQogICAgfQoKICAgIC8vY2hlY2tzIHJvbWFuIG51bWVyYWxzIGZvcm1hdCAobWF4IG51bWJlciBpcyAzOTk5ID0gTU1NQ01YQ0lYKQogICAgLy9Ac2VlIGh0dHA6Ly93Li4uY29udGVudC1hdmFpbGFibGUtdG8tYXV0aG9yLW9ubHkuLi5lLnJvL3JlZ3VsaV9zY3JpZXJlX2NpZnJlX251bWVyZV9yb21hbmUucGhwP2xhbmc9ZW4KICAgIHByaXZhdGUgc3RhdGljIGZpbmFsIFBhdHRlcm4gQ0hFQ0tfUk9NQU5fTlVNRVJBTFNfRk9STUFUX1JFR0VYID0KICAgICAgICAgICAgUGF0dGVybi5jb21waWxlKCJeKE17MCwzfSkoQ018Q0R8RD9DezAsM30pKFhDfFhMfEw/WHswLDN9KShJWHxJVnxWP0l7MCwzfSkkIik7CgogICAgLy93ZSBuZWVkIHRoaXMgY29tcGlsZWQgcmVnZXggdG8gbWF0Y2ggZWFjaCByb21hbiBudW1lcmFsIG9uZSBieSBvbmUKICAgIC8vZnJvbSBsZWZ0IHRvIHJpZ2h0IGZyb20gdGhlIGhpZ2hlc3QgdG8gdGhlIGxvd2VzdCBkZWNpbWFsIHZhbHVlCiAgICBwcml2YXRlIHN0YXRpYyBmaW5hbCBQYXR0ZXJuIEZJTkRfUk9NQU5fTlVNRVJBTFNfT05FX0JZX09ORV9SRUdFWCA9CiAgICAgICAgICAgIFBhdHRlcm4uY29tcGlsZSgiTXxDTXxEfENEfEN8WEN8THxYTHxYfElYfFZ8SVZ8SSIpOwoKICAgIHByaXZhdGUgc3RhdGljIGJvb2xlYW4gY29udGFpbnNWYWxpZFJvbWFuTnVtZXJhbHMoU3RyaW5nIHMpIHsKICAgICAgICByZXR1cm4gIShzID09IG51bGwgfHwgcy5pc0VtcHR5KCkgfHwgIUNIRUNLX1JPTUFOX05VTUVSQUxTX0ZPUk1BVF9SRUdFWC5tYXRjaGVyKHMpLm1hdGNoZXMoKSk7CiAgICB9CgogICAgLyoqCiAgICAgKiBDb252ZXJ0cyByb21hbiBudW1lcmFscyB0byB0aGVpciBkZWNpbWFsIHZhbHVlCiAgICAgKiBAcGFyYW0gcyBTdHJpbmcgY29udGFpbmluZyByb21hbiBudW1lcmFscwogICAgICogQHJldHVybiBkZWNpbWFsIHZhbHVlIGNvcnJlc3BvbmRpbmcgdG8gdGhlIHJvbWFuIG51bWVyYWxzIGlucHV0CiAgICAgKi8KICAgIHB1YmxpYyBzdGF0aWMgaW50IGNvbnZlcnQoU3RyaW5nIHMpIHsKICAgICAgICBpZiAoIWNvbnRhaW5zVmFsaWRSb21hbk51bWVyYWxzKHMpKSByZXR1cm4gLTE7CgogICAgICAgIGZpbmFsIE1hdGNoZXIgbWF0Y2hlciA9IEZJTkRfUk9NQU5fTlVNRVJBTFNfT05FX0JZX09ORV9SRUdFWC5tYXRjaGVyKHMpOwoKICAgICAgICBpbnQgc3VtID0gMDsKICAgICAgICAvL2V2ZXJ5IHRpbWUgd2UgZmluZCBhIHJvbWFuIG51bWVyYWwuLi4KICAgICAgICB3aGlsZSAobWF0Y2hlci5maW5kKCkpIHsKICAgICAgICAgICAgLy8uLi5sZXQncyBhZGQgaXRzIGRlY2ltYWwgdmFsdWUgdG8gdGhlIHN1bQogICAgICAgICAgICBzdW0gKz0gUk9NQU5fTlVNRVJBTFNfVE9fREVDSU1BTF9NQVAuZ2V0KG1hdGNoZXIuZ3JvdXAoMCkpOwogICAgICAgIH0KCiAgICAgICAgcmV0dXJuIHN1bTsKICAgIH0KfQ==