Tim Varley Logo
Tim Varley Principal AI Engineer and Tech Leader
Problem #89 medium

Roman numerals

For a number written in Roman numerals to be considered valid there are basic rules which must be followed. Even though the rules allow some numbers to be expressed in more than one way there is always a "best" way of writing a particular number.

For example, it would appear that there are at least six ways of writing the number sixteen:

IIIIIIIIIIIIIIII
VIIIIIIIIIII
VVIIIIII
XIIIIII
VVVI
XVI

However, according to the rules only XIIIIII and XVI are valid, and the last example is considered to be the most efficient, as it uses the least number of numerals.

The 11K text file, roman.txt (right click and 'Save Link/Target As...'), contains one thousand numbers written in valid, but not necessarily minimal, Roman numerals; see About Roman Numerals for the definitive rules for this problem.

Find the number of characters saved by writing each of these in their minimal form.

Note: You can assume that all the Roman numerals in the file contain no more than four consecutive identical units.

View on Project Euler

Implementations

cpp
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
int roman_to_int(const std::string& s) {
std::vector<std::pair<std::string, int>> values = {
{"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}
};
int total = 0;
size_t i = 0;
while (i < s.length()) {
bool found = false;
for (const auto& p : values) {
if (s.substr(i, p.first.length()) == p.first) {
total += p.second;
i += p.first.length();
found = true;
break;
}
}
if (!found) i++;
}
return total;
}
std::string int_to_roman(int num) {
std::vector<std::pair<int, std::string>> values = {
{1000, "M"}, {900, "CM"}, {500, "D"}, {400, "CD"},
{100, "C"}, {90, "XC"}, {50, "L"}, {40, "XL"},
{10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"}
};
std::string result;
for (const auto& p : values) {
while (num >= p.first) {
result += p.second;
num -= p.first;
}
}
return result;
}
long long roman_numerals() {
std::ifstream file("src/p089_roman.txt");
if (!file) return 0;
std::string line;
long long saved = 0;
while (std::getline(file, line)) {
if (line.empty()) continue;
int value = roman_to_int(line);
std::string minimal = int_to_roman(value);
saved += line.length() - minimal.length();
}
return saved;
}

Solution Notes

Mathematical Background

Roman numerals can be written in multiple valid forms, but each number has a minimal representation using the fewest characters. The problem involves parsing 1000 Roman numerals from a file and calculating the total character savings when converting them to their minimal forms.

Algorithm Analysis

The solution uses two functions: one to convert Roman numerals to integers using a lookup table of symbol values, and another to convert integers back to minimal Roman numerals. For each numeral in the file, it computes the value, generates the minimal form, and accumulates the difference in lengths.

Performance

All implementations are highly efficient, running in under 10ms due to the small input size (1000 lines) and simple lookup-based conversions.

Key Insights

  • The parsing handles subtractive notation (e.g., IV for 4) by checking for two-character combinations first.
  • The minimal form generation uses greedy selection from largest to smallest values.

Educational Value

This problem demonstrates string parsing techniques and the importance of canonical representations in numeral systems, providing insight into historical number notation.

Problem #89 is taken from Project Euler and licensed under CC BY-NC-SA 4.0.