c++ 将罗马数字转换为小数

问题描述 投票:0回答:4

这个程序是我刚刚参加的考试的一部分,我必须写。我只走到了这一步,却哪儿也去不了。提示如下:“编写一个测试函数 toDecimal(),将罗马数字(例如 MMLXVII)转换为其十进制数字表示形式。使用 Main() 测试该函数。toDecimal() 函数应该有 2 个参数,即字符串数组罗马数字和辅助函数。该辅助函数将返回罗马数字中使用的每个字母的数值。然后按如下方式转换字符串参数:查看前两个字符,如果第一个较大,则转换第一个和将其添加到求和中,然后使用第二个值再次调用转换函数并将两者相加。如果第一个字符小于第二个字符,则从第二个字符中减去第一个字符,并将结果添加到字符串的转换中。无需验证也会转换像“IC”这样的字符串。验证字符串争论,如果有错误,调用错误处理函数。提供至少两个错误处理函数,并分别测试 toDecimal()。其中一个可能会建议用户更正,其他人可以纠正它。”

I,X,C,M 不能连续重复超过 3 次,D,L,V 不能连续重复。I 只能从 V 中减去,X,X 只能从 L 和 C 中减去, C 只能从 D 和 M 中减去。V、L 和 D 永远不能相减。

我为此失眠了大约 2 天,尝试使用和打破规则以数百种不同的方式编写它。这是我所得到的最接近的。

#include <iostream>
#include <string>
#include <map>
#include <algorithm>
#include <cstring>
using namespace std;

bool checker(string roman);
// Adds each value of the roman numeral together
int toDecimal(string, bool* (*function)(string));
int convert(string roman, int i);

int main(){
    string roman;
    cout << "This program takes a roman numeral the user enters then converts it to decimal notation." << endl;
    cout << "Enter a roman numeral: ";
    cin >> roman;
    transform(roman.begin(), roman.end(), roman.begin(), toupper);
    cout << roman << " is equal to " << toDecimal(roman,  *checker(roman)) << endl;
}

bool checker(string roman){
    int length = roman.length();
    for (int count = 0; count < length; count++){
        string sub = roman.substr(count, count);
        if(sub != "I" || sub != "V" || sub != "X" || sub != "L" || sub != "C" || sub != "D" || sub != "M"){
            cout << "Error. Try Again"<< endl;
            return false;
        }
        else if(convert(roman, count) == convert(roman, count-1) && convert(roman, count) == convert(roman, count+1)){
            if (convert(roman,count) == 1 || convert(roman,count) == 10 || convert(roman,count) == 100 || convert(roman,count) == 1000)
                if(convert(roman, count-1) == convert(roman, count-2) || convert(roman, count+1) == convert(roman, count+2)){
                    cout << "Error Try again" << endl;
                    return false;
                }
            else if (convert(roman,count) == 5 || convert(roman,count) == 50 || convert(roman,count) == 500){
                cout << "Error Try again" << endl;
                    return false;
            }
            else return true;

        }           
    }
    return true;
}

int toDecimal(string s, bool*(checker) (string roman)){
    /**map<char, int> roman;
    roman['M'] = 1000;
    roman['D'] = 500;
    roman['C'] = 100;
    roman['L'] = 50;
    roman['X'] = 10;
    roman['V'] = 5;
    roman['I'] = 1;*/
    checker(s);
    int res = 0;
    for (int i = 0; i < s.length() - 1; ++i){
        int num = convert(s,i);
        res += num;
        /**if (roman[s[i]] < roman[s[i+1]])
            res -= roman[s[i]];
        else
            res += roman[s[i]];
    }
    res += roman[s[s.size()-1]];*/}
    return res;
}

int convert(string roman, int i){
    enum romans {I = 1, V = 5, X = 10, L = 50, C = 100, D = 500, M = 1000};
    int num = 0;
    char c = roman[0]; 
    switch(c){
        case 'M': 
            num = M; break;
        case 'D':   
            if(i + 1 != roman.size() && roman[i+1] == 'M'){
                num = M - D;break;
            }
            else
                num = D; break;
        case 'C': 
            if(i + 1 != roman.size() && roman[i+1] == 'M' || roman[i+1] == 'D'){
                if(roman[i+1] == 'M') num = M - C; break;
                if(roman[i+1] == 'D') num = D - C; break;
            }
            else
                num = C; break;
        case 'L':
            if(i + 1 != roman.size() && roman[i+1] == 'M' || roman[i+1] == 'D' || roman[i+1] == 'C'){
                if(roman[i+1] == 'M') num = M - L; break;
                if(roman[i+1] == 'D') num = D - L; break;
                if(roman[i+1] == 'C') num = C - L; break;
                }
            else
                num = L; break;
        case 'X': 
            if(i + 1 != roman.size() && roman[i+1] == 'M' || roman[i+1] == 'D' || roman[i+1] == 'C'|| roman[i+1] == 'L'){
                if(roman[i+1] == 'M') num = M - X; break;
                if(roman[i+1] == 'D') num = D - X; break;
                if(roman[i+1] == 'C') num = C - X; break;
                if(roman[i+1] == 'L') num = C - X; break;
            }
                num = X; break;
        case 'V':
            if(i + 1 != roman.size() && roman[i+1] == 'M' || roman[i+1] == 'D' || roman[i+1] == 'C'|| roman[i+1] == 'L' || roman[i+1] == 'X'){
                if(roman[i+1] == 'M') num = M - V; break;
                if(roman[i+1] == 'D') num = D - V; break;
                if(roman[i+1] == 'C') num = C - V; break;
                if(roman[i+1] == 'L') num = L - V; break;
                if(roman[i+1] == 'X') num = X - V; break;
            }
                num = V; break;
        case 'I':
            if ( i + 1 != roman.size() && roman[i + 1] != 'I'){
                if(roman[i+1] == 'M') num = M - I; break;
                if(roman[i+1] == 'D') num = D - I; break;
                if(roman[i+1] == 'C') num = C - I; break;
                if(roman[i+1] == 'L') num = L - I; break;
                if(roman[i+1] == 'X') num = X - I; break;
            }
                num =1; break;
    }
    return num;
}

** 我在这里添加了人们的帮助。这是显示进度/会议的编辑。

c++ visual-c++
4个回答
13
投票

这是我用来将罗马数字(小于 3999)转换为整数的代码。您可以检查它是否适用于更大的数字。

int romanToInt(string s) {
    map<char, int> roman;
    roman['M'] = 1000;
    roman['D'] = 500;
    roman['C'] = 100;
    roman['L'] = 50;
    roman['X'] = 10;
    roman['V'] = 5;
    roman['I'] = 1;

    int res = 0;
    for (int i = 0; i < s.size() - 1; ++i)
    {
        if (roman[s[i]] < roman[s[i+1]])
            res -= roman[s[i]];
        else
            res += roman[s[i]];
    }
    res += roman[s[s.size()-1]];
    return res;
}

希望这可以帮助你。


3
投票

Annie Kim 提供的解决方案有效,但它使用了

std::map
,多次查询同一个角色,但我看不出其中的原因。

int convert_roman_digit(char d)
{
    switch (d)
    {
        case 'M': return 1000;
        case 'D': return 500;
        case 'C': return 100;
        case 'L': return 50;
        case 'X': return 10;
        case 'V': return 5;
        case 'I': return 1;
        default: throw std::invalid_argument("Invalid digit");
    }
}

int roman_to_int(const std::string& roman)
{
    int result = 0, last_added = 0;

    for (auto it = roman.rbegin(); it != roman.rend(); ++it)
    {
        const int value = convert_roman_digit(*it);
        if (value >= last_added)
        {
            result += value;
            last_added = value;
        }
        else
        {
            result -= value;
        }
    }

    return result;
}

警告:该函数很乐意接受一些无效输入(例如

IMM
),包括“负”数(例如
IIIIIIIIIIIIIX
),没有溢出检查,并且会抛出异常。请随意改进它。


0
投票
int romanToInt(string s)
{
    unordered_map<char, int> roman;
    roman['I'] = 1;
    roman['V'] = 5;
    roman['X'] = 10;
    roman['L'] = 50;
    roman['C'] = 100;
    roman['D'] = 500;
    roman['M'] = 1000;

    int num = 0, prev = 0, curr;
    for (int i = s.length() - 1; i >= 0; i--)
    {
        curr = roman[s[i]];
        num += (curr >= prev ? 1 : -1) * curr;
        prev = curr;
    }
    return num;
}

0
投票

此答案是此处发布的答案的附录。

它提供了该答案中程序的源代码列表。

创建附录是因为原始答案中没有足够的空间来发布它使用的五个文件。

主.cpp

// main.cpp
#include <iomanip>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <string>

#include "RomanNumeral.h"
#include "tbx.utility.h"

namespace
{
    bool test_valid_Roman_numerals(std::ostream& log)
    {
        log << "Round-trip Conversion of Every Valid Roman Numeral\n\n";
        int n_failures{};
        for (int n{ 1 }; n <= 3999; ++n)
        {
            auto r{ roman_numeral::fromInteger(n) };
            auto num{ roman_numeral::toDecimal(r) };
            if (num != n)
                ++n_failures;
        }
        log << "  Failure count: " << n_failures << "\n\n";
        return n_failures == 0;
    }
    bool test_invalid_Roman_numeral(std::ostream& log, std::string const& r)
    {
        auto const d = roman_numeral::toDecimal(r);
        auto const v = roman_numeral::isValid(r);
        log << "   " << std::left << std::setw(11) << std::quoted(r)
            << "   " << std::right << std::setw(9) << d
            << "   " << std::left << std::boolalpha << v
            << '\n';
        return !v;
    }
    bool test_invalid_Roman_numerals(std::ostream& log)
    {
        log << "Detect Invalid Roman Numerals\n\n"
            << "   " << "Roman\n"
            << "   " << std::left << std::setw(11) << "Numeral"
            << "   " << std::right << std::setw(9) << "toDecimal"
            << "   " << std::left << "isValid"
            << "\n\n";
        tbx::OneTimeToggle pass_all{ true };
        pass_all = test_invalid_Roman_numeral(log, "");          // empty string
        pass_all = test_invalid_Roman_numeral(log, "garbage");   // yeah, garbage
        pass_all = test_invalid_Roman_numeral(log, "I I");       // space between symbols
        pass_all = test_invalid_Roman_numeral(log, "IL");        // invalid subtraction
        pass_all = test_invalid_Roman_numeral(log, "IC");
        pass_all = test_invalid_Roman_numeral(log, "ID");
        pass_all = test_invalid_Roman_numeral(log, "IM");
        pass_all = test_invalid_Roman_numeral(log, "VX");        // only 'I`, 'X', and 'C'
        pass_all = test_invalid_Roman_numeral(log, "VL");        // can be subtracted
        pass_all = test_invalid_Roman_numeral(log, "VC");
        pass_all = test_invalid_Roman_numeral(log, "VD");
        pass_all = test_invalid_Roman_numeral(log, "VM");
        pass_all = test_invalid_Roman_numeral(log, "XD");
        pass_all = test_invalid_Roman_numeral(log, "XM");
        pass_all = test_invalid_Roman_numeral(log, "LC");
        pass_all = test_invalid_Roman_numeral(log, "LD");
        pass_all = test_invalid_Roman_numeral(log, "LM");
        pass_all = test_invalid_Roman_numeral(log, "DM");
        pass_all = test_invalid_Roman_numeral(log, "IVIV");      // `I`, `X`, and `C` can only
        pass_all = test_invalid_Roman_numeral(log, "IXIX");      // be subtracted once in any 
        pass_all = test_invalid_Roman_numeral(log, "IXIV");      // given Roman numeral
        pass_all = test_invalid_Roman_numeral(log, "IVIX");
        pass_all = test_invalid_Roman_numeral(log, "XCXL");
        pass_all = test_invalid_Roman_numeral(log, "CDCM");
        pass_all = test_invalid_Roman_numeral(log, "VV");        // only 'I`, 'X', 'C' and 'M'
        pass_all = test_invalid_Roman_numeral(log, "LL");        // can be repeated
        pass_all = test_invalid_Roman_numeral(log, "DD");
        pass_all = test_invalid_Roman_numeral(log, "IIII");      // too many repeats
        pass_all = test_invalid_Roman_numeral(log, "XXXX");
        pass_all = test_invalid_Roman_numeral(log, "CCCC");
        pass_all = test_invalid_Roman_numeral(log, "MMMM");
        pass_all = test_invalid_Roman_numeral(log, "IXX");       // repetition and subtraction
        pass_all = test_invalid_Roman_numeral(log, "XCC");       // cannot be combined
        pass_all = test_invalid_Roman_numeral(log, "CMM");
        pass_all = test_invalid_Roman_numeral(log, "IIV");       // "threshold" not increasing
        pass_all = test_invalid_Roman_numeral(log, "IIX");
        pass_all = test_invalid_Roman_numeral(log, "XXL");
        pass_all = test_invalid_Roman_numeral(log, "XXC");
        pass_all = test_invalid_Roman_numeral(log, "CCD");
        pass_all = test_invalid_Roman_numeral(log, "CCM");
        pass_all = test_invalid_Roman_numeral(log, "VIX");
        pass_all = test_invalid_Roman_numeral(log, "LXC");
        pass_all = test_invalid_Roman_numeral(log, "DCM");
        log << "\n\n";
        return pass_all;
    }
    int get_int(
        std::string const& prompt,
        int const min,
        int const max,
        std::istream& ist,
        std::ostream& ost)
    {
        for (;;)
        {
            ost << prompt;
            if (int n{}; !(std::cin >> n))
            {
                ost << "Invalid entry. Please reenter.\n\n";
                ist.clear();
                ist.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            }
            else if (n < min || n > max)
            {
                ost << "Invalid entry. Please reenter.\n"
                    << "Entries must be between " << min << " and " << max << ".\n\n";
            }
            else
            {
                ist.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
                return n;
            }
        }
    }
    std::string get_Roman_numeral(std::istream& ist, std::ostream& ost)
    {
        std::string r;
        for (;;)
        {
            ost << "Enter a roman numeral: ";
            ist >> r;
            ost.put('\n');
            if (roman_numeral::isValid(r))
                break;
            ost << "Invalid entry. Please reenter.\n\n";
        }
        ist.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        return r;
    }
    void select_error_processing_function(
        std::ostream& log,
        std::istream& ist,
        std::string& r)
    {
        char const* const menu
        {
            " is not a valid Roman numeral."
            "\n"
            "\nSelect an error processing function:"
            "\n"
            "\n 1. Reenter the Roman numeral"
            "\n 2. Throw an exception"
            "\n\n"
        };
        enum { ReenterRomanNumeral = 1, ThrowException };
        log << r << menu;
        int choice{ get_int("Choice? ", 1, 2, ist, log) };
        log.put('\n');
        switch (choice)
        {
        case ReenterRomanNumeral:
            r = get_Roman_numeral(ist, log);
            break;
        case ThrowException:
            throw std::runtime_error("Malformed Roman numeral: \"" + r + '"');
        default:
            // std::unreachable();
            break;
        }
    }
    bool test_Roman_numeral_entered_by_user(
        std::istream& ist,
        std::ostream& log)
    {
        log << "Roman Numeral to Integer Converter\n\n"
            "  Enter a Roman numeral: ";
        std::string r;
        std::getline(ist, r);
        log.put('\n');
        int num{};
        while ((num = roman_numeral::toDecimal(r)) == 0)
        {
            try { select_error_processing_function(log, ist, r); }
            catch (std::exception const& e) {
                log << "  Exception caught in function "
                    "`test_Roman_numeral_entered_by_user`: "
                    << e.what() << "\n\n";
                return false;
            }
        }
        log << "  " << r << " = " << num << "\n\n";
        return true;
    }
}
int main()
{
    auto& log{ std::cout };  // substitute log files, as needed
    auto& ist{ std::cin };
    int exit_code{}, shift{};

    // Each of the three tests gets its own bit in the `exit_code`,
    // where 0-valued bits indicate success.
    exit_code |= test_valid_Roman_numerals(log) ? 0 : 1 << shift++;
    exit_code |= test_invalid_Roman_numerals(log) ? 0 : 1 << shift++;
    exit_code |= test_Roman_numeral_entered_by_user(ist, log) ? 0 : 1 << shift++;
    return exit_code;  // between 0 and 7
}
// end file: main.cpp

罗马数字.h

#pragma once
// RomanNumeral.h
// Functions designed per the specification at Stack Overflow
// https://stackoverflow.com/q/17724887/22193627

#include <string>

namespace roman_numeral
{
    bool isValid(std::string const& r);
    int value_from_map(char const symbol);
    int value_from_switch(char const symbol);
    int toDecimal(
        std::string r, 
        int (*value)(char const symbol) = value_from_map);
    std::string fromInteger(int const n);
}
// end file: RomanNumeral.h

罗马数字.cpp

// RomanNumeral.cpp
#include <cstddef>
#include <iterator>
#include <string>
#include <unordered_map>
#include <unordered_set>

#include "RomanNumeral.h"
#include "tbx.utility.h"

namespace
{
    //==================================================================
    // is_repeatable
    //==================================================================
    bool is_repeatable(char const i)
    {
        return i == 'I'
            || i == 'X'
            || i == 'C'
            || i == 'M';
    }
    //==================================================================
    // is_subtractive
    //==================================================================
    bool is_subtractive(char const i, char const vx)
    {
        return i == 'I' && vx == 'V'
            || i == 'I' && vx == 'X'
            || i == 'X' && vx == 'L'
            || i == 'X' && vx == 'C'
            || i == 'C' && vx == 'D'
            || i == 'C' && vx == 'M';
    }
}
namespace roman_numeral
{
    //==================================================================
    // fromInteger
    //==================================================================
    std::string fromInteger(int const n)
    {
        if (n < 1 || n > 3999)
            return "";  // sentinel value signals failure

        static constexpr char const* const I[]{ "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" };
        static constexpr char const* const X[]{ "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" };
        static constexpr char const* const C[]{ "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" };
        static constexpr char const* const M[]{ "", "M", "MM", "MMM" };

        auto const v{ static_cast<std::size_t>(n) };
        enum : std::size_t { fifteen = 15u };
        std::string r;
        r.reserve(fifteen); // room enough for "MMMDCCCLXXXVIII", the widest Roman numeral
        r = M[v / 1000u];
        r += C[(v % 1000u) / 100u];
        r += X[(v % 100u) / 10u];
        r += I[v % 10u];;
        return r;
    }
    //==================================================================
    // isValid
    //==================================================================
    bool isValid(std::string const& r)
    {
        return toDecimal(r, value_from_map) != 0;
    }
    //==================================================================
    // toDecimal
    //==================================================================
    int toDecimal(std::string r, int (*value)(char const symbol))
    {
        r = tbx::to_upper(tbx::trim_whitespace(r));

        // Fail if any of the characters in `r` are not valid Roman 
        // numeral symbols.
        for (auto const& c : r)
            if (value(c) == 0)
                return 0;  // sentinel value signals failure

        // Handle short strings as special cases.
        enum : std::size_t { zero, one };
        switch (r.length())
        {
        case zero: return 0;  // sentinel value signals failure
        case one: return value(r.front());
        default: break;
        }

        // Loop, scanning string `r` from right-to-left.
        auto trailer{ r.crbegin() };
        auto t{ *trailer };
        auto vt{ value(t) };

        auto leader{ std::next(r.crbegin()) };
        auto l{ *leader };
        auto vl{ value(l) };

        int sum{ vt };
        int n_repeats{ 1 };
        int threshold{ vt };

        std::unordered_set<char> already_subtracted;

        while (leader != r.crend())
        {
            l = *leader; vl = value(l);
            t = *trailer; vt = value(t);
            if (vl < threshold)
            {
                if (!is_subtractive(l, t) || already_subtracted.count(l) || n_repeats != 1)
                    return 0;  // sentinel value signals failure
                sum -= vl;
                already_subtracted.insert(l);
                n_repeats = 0;
                threshold = 10 * vl;
            }
            else if (vl == threshold)
            {
                if (!is_repeatable(l) || ++n_repeats > 3)
                    return 0;  // sentinel value signals failure
                sum += vl;
            }
            else  // vl > threshold
            {
                sum += vl;
                n_repeats = 1;
                threshold = vl;
            }
            ++leader; ++trailer;
        }
        return sum;
    }
    //==================================================================
    // value_from_map
    //==================================================================
    int value_from_map(char const symbol)
    {
        static const std::unordered_map<char, int> values =
        {
            { 'I', 1 },
            { 'V', 5 },
            { 'X', 10 },
            { 'L', 50 },
            { 'C', 100 },
            { 'D', 500 },
            { 'M', 1000 }
        };
        if (auto const it{ values.find(symbol) }; it == values.end())
            return 0;  // sentinel value signals failure
        else
            return it->second;
    }
    //==================================================================
    // value_from_switch
    //==================================================================
    int value_from_switch(char const symbol)
    {
        switch (symbol)
        {
        case 'I': return 1;
        case 'V': return 5;
        case 'X': return 10;
        case 'L': return 50;
        case 'C': return 100;
        case 'D': return 500;
        case 'M': return 1000;
        default: break;
        }
        return 0;  // sentinel value signals failure
    }
}
// end file: RomanNumeral.cpp

tbx.utility.h

#pragma once
// tbx.utility.h
#include <string>
#include <string_view>

namespace tbx
{
    auto to_upper(std::string s) noexcept -> std::string;
    void to_upper_in_place(std::string& s) noexcept;
    auto trim_whitespace(std::string const& s) -> std::string;
    auto trim_whitespace_view(std::string_view sv) noexcept -> std::string_view;

    //==================================================================
    // OneTimeToggle
    //==================================================================
    class OneTimeToggle
    {
        bool const initial_state;
        bool state;
    public:
        constexpr OneTimeToggle(bool const b) noexcept
            : initial_state{ b }, state{ b }
        {}
        constexpr bool operator() () const noexcept {
            return state;
        }
        constexpr operator bool() const noexcept {
            return state;
        }
        constexpr OneTimeToggle& operator= (bool const b) noexcept {
            if (b != initial_state)
                state = b;
            return *this;
        }
        constexpr void reset() {
            state = initial_state;
        }
    };
}
// end file: tbx.utility.h

tbx.utility.cpp

// tbx.utility.cpp
#include <algorithm>
#include <cctype>
#include <string>
#include <string_view>

#include "tbx.utility.h"

namespace tbx
{
    //==================================================================
    // to_upper
    //==================================================================
    std::string to_upper(std::string s) noexcept
    {
        tbx::to_upper_in_place(s);
        return s;
    }
    //==================================================================
    // to_upper_in_place
    //==================================================================
    void to_upper_in_place(std::string& s) noexcept
    {
        std::transform(s.begin(), s.end(), s.begin(),
            [](unsigned char c) {
                return static_cast<char>(std::toupper(c));
            }
        );
    }
    //==================================================================
    // trim_whitespace
    //==================================================================
    auto trim_whitespace(std::string const& s) -> std::string
    {
        return std::string{ trim_whitespace_view(s) };
    }
    //==================================================================
    // trim_whitespace_view
    //==================================================================
    auto trim_whitespace_view(std::string_view sv) noexcept -> std::string_view
    {
        // Trim leading and trailing whitespace from string_view `sv`.
        auto const first{ sv.find_first_not_of(" \f\n\r\t\v") };
        if (first == std::string_view::npos)
            return {};
        auto const last{ sv.find_last_not_of(" \f\n\r\t\v") };
        enum : std::string_view::size_type { one = 1u };
        return sv.substr(first, (last - first + one));
    }
}
// end file: tbx.utility.cpp
© www.soinside.com 2019 - 2024. All rights reserved.