罗马数字减去而没有转换

时间:2014-02-26 11:37:48

标签: c# algorithm roman-numerals

可以在不转换为十进制数的情况下减去罗马数字吗?

例如

X - III = VII

所以在输入中我有 X III 。在输出中,我有 VII

我需要算法而不转换为十进制数。

现在我没有想法。

7 个答案:

答案 0 :(得分:2)

最简单的算法是创建 - 罗马人的功能。减去A-B意味着同时重复A--和B--,直到B中没有任何内容为止。


但我想做更有效的事情

罗马数字可以用一种非常微弱的方式看作位置。我们会用它。

让我们做一个减法的简短表格:

X-V=V

X-I=IX
IX-I=VIII
VIII-I=VII
VII-I=VI
VI-I=V
V-I=IV
IV-I=III
III-I=II
II-I=I
I-I=_

另外:

V+I=VI

对于CLX和MDC级别也是如此。当然,您只能创建一个表,但可以通过替换字母在不同的级别上使用它。

  • 我们取数字,例如,A = MMDCVI = 2606 a B = CCCXLIII = 343
  • 让我们将它们分配到等级= 10的幂。以下几个操作仅在内部级别。

    A = MM + DC + VI,B = CCC + XL + III

  • 然后减去

    A-B = MM +(DC-CCC)+( - XL)+(VI-III)

  • 在每个级别,我们有三个可能的字母:单位,五个单位和十个单位。组合(单位,五单位)和(单位,十单位)将转换为差异

    A-B = MM +(DC-CCC)+( - L + X)+(VI-III)

  • 正常组合(高级符号在初级符号之前)将被翻译成总和。

    A-B = MM +(D + C-C-C-C)+( - L + X)+(V + I-I-I-I)

  • 缩短相同符号的组合

    A-B = MM +(D-C-C)+( - L + X)+(V-I-I)

  • 如果某个级别为负数,则从高级别借用一个单位。当然,它可以通过空白级别工作。

    A-B = MM +(D-C-C-C)+(C-L + X)+(V-I-I)

  • 现在,在每个级别,我们都会应用我们制作的减法表,减去每个缩小的符号,从表格的顶部开始并重复它,直到没有剩下的小成员为止。

    A-B = MM +(CD-C-C)+(L + X)+(IV-I)

    A-B = MM +(CCC-C)+(L + X)+(III)

    A-B = MM +(CC)+(L + X)+(III)

  • 现在,使用添加表

    A-B = MM +(CC)+(LX)+(III)

  • 现在,我们将打开括号。如果在某个级别中有“_”,那么就没有任何内容。

    A-B = MMCCLXIII = 2263

结果是正确的。

答案 1 :(得分:1)

除了简单地展开整个罗马数字之外,还有一个更优雅的解决方案。这样做的缺点是O(n)的复杂性,而不是O(log n),其中n是输入数。

我觉得这个任务非常有趣。没有转换确实是可能的。基本上,你只需查看最后一位数字。如果它们匹配,将它们带走,如果不匹配,则更换较大的一个。然而,整个任务因“IV”这样的数字而变得更加复杂,因为你需要前瞻。

这是代码。由于这很可能是家庭作业,我拿出了一些代码,所以你必须自己思考,其余的应该是什么样的。

    private static char[] romanLetters = { 'I', 'V', 'X', 'L', 'C', 'D', 'M' };
    private static string[] vals = { "IIIII", "VV", "XXXXX", "LL", "CCCCC", "DD" };

    static string RomanSubtract(string a, string b)
    {
        var _a = new StringBuilder(a);
        var _b = new StringBuilder(b);

        var aIndex = a.Length - 1;
        var bIndex = b.Length - 1;

        while (_a.Length > 0 && _b.Length > 0)
        {
            if (characters match)
            {
                if (lookahead for a finds a smaller char)
                {
                    aIndex = ReplaceRomans(_a, aIndex, aChar);
                    continue;
                }
                if (lookahead for b finds a smaller char)
                {
                    bIndex = ReplaceRomans(_b, bIndex, bChar);
                    continue;
                }
                _a.Remove(aIndex, 1);
                _b.Remove(bIndex, 1);
                aIndex--;
                bIndex--;
            }
            else if (aChar > bChar)
            {
                aIndex = ReplaceRomans(_a, aIndex, aChar);
            }
            else
            {
                bIndex = ReplaceRomans(_b, bIndex, bChar);
            }
        }

        return _a.Length > 0 ? _a.ToString() : "-" + _b.ToString();
    }

    private static int ReplaceRomans(StringBuilder roman, int index, int charIndex)
    {
        if (index > 0)
        {
            var beforeChar = Array.IndexOf(romanLetters, roman[index - 1]);
            if (beforeChar < charIndex)
            {
                Replace e.g. IX with VIIII
            }
        }
        Replace e.g. V with IIIII
    }

答案 2 :(得分:0)

除了检查输入数字的每个可能组合之外 - 假设输入有限 - 没有办法做你所要求的。罗马数字在数学运算方面很糟糕。

您可以编写一个不转换它们的算法,但它必须在某些时候使用十进制数。或者您可以将它们标准化为例如“IIIII ......”,但你需要再写一些像“50 chars = L”这样的等价词。

答案 3 :(得分:0)

粗略的想法:

创建“地图”或每个罗马数字与简单数字相关的列表,例如IV对应于(II + II),而V对应于(III + II),X对应于(V + V)

计算时,例如X - III,这不是一个数学术语,而是一个字符串,可以分几个步骤进行更改,每次检查从减号运算符的两边删除的东西:

x - III                       // Nothing to remove
(V + V) - III                 // Still nothing to remove
(III + II + III + II) - III   // NOW we can remove a "III" from both sides
                              // while still treating these as roman numerals.

Result:      III + II + II
Rejoined:   V + II = VII. 

如果你使每个数字对应于“地图”中尽可能简单的东西(例如III可以对应于(II + I),那么你就不会遇到遗留问题),那么我很漂亮确定你可以在这里找到某种解决方案。

当然,这需要一堆字符串操作,比较和地图,您的算法可以从中“知道”如何比较或切换值。不完全是传统的数学,但是我认为 罗马数字是如何工作的。

答案 4 :(得分:0)

我的想法的基本草图是构建通过迭代器或可观察对象链接在一起的简单转换器。

因此,举例来说,在输入方面你有CConverter执行组合CDCMD和{{1}的转换分别分为MCCCCCCCCCCCCCCCCCC。所有其他收到的输入都是通过不受干扰的。然后,行CCCCCCCCCC中的下一个转换器将XConverterXLXCL转换为适当数量的X,依此类推直到你只有一个所有X的流。

然后通过同步消耗这两个I s流来执行减法。如果I先用完,那么答案是minuend或否定,在这种情况下,一切都出错了。否则,当0用完时,您只需从minuend开始发出所有剩余的subtrahend

现在你需要转换回来。因此,第一个I排队INormalizer直到收到其中五个,然后它会发出I。如果它到达流的末尾并且它收到4,则它会发出V。否则它只会在流结束时发出尽可能多的IV个s,然后结束自己的流。

接下来,I排队VNormalizer,直到收到两个,然后发出V。如果它收到X并且其中有一个排队IV,则会发出V,否则会发出IX

如果它正在接收的流结束或者只是开始发送IV并且它仍然有I排队,那么它会发出,然后发送流想要发送的任何其他内容,然后结束它自己的流。

依此类推,重新构建正确的罗马数字。

答案 5 :(得分:0)

解析输入字符串以对混合的5/10基数(M,D,C,L,X,I)中的数字进行分组。即MMXVII产生MM |||| X | V | II。

现在通过成对取消数字从右到左减去。即V | III - II = V | II - I = V | I.

如果需要,可以借用,即分割下一个最高位(V分割为IIIII,X分割为VV ......)。示例:V | I - III = V | - II = IIIII - II = III。借用可能需要递归,如X || I - III = X || - II = VV | - II = V | IIIII - II = V | III。

前缀表示法(IV,IX,XL,XC ......)使它更复杂一些。一种方法是预处理字符串以在输入时删除它们(替换为IIII,VIIII,XXXX,LXXXX ......)和后处理以在输出时恢复它们。

示例:

XCIX - LVI = LXXXXVIIII - LVI = L | XXXX | V | IIII - L | V | I = L | XXXX | V | III - L | V | = L | XXXX || III - L || = XXXX || III = XXXXXIII = XLIII

纯字符处理,不涉及算术。

Digits= "MDCLXVI"
Divided= ["DD", "CCCCC", "LL", "XXXXX", "VV", "IIIII"]

def In(Input):
    return Input.replace("CM", "DCCCC").replace("CD", "CCCC").replace("XC", "LXXXX").replace("XL", "XXXX").replace("IX", "VIIII").replace("IV", "IIII")

def Group(Input):
    Groups= []
    for Digit in Digits:
        # Split after the last digit
        m= Input.rfind(Digit) + 1
        Groups.append(Input[:m])
        Input= Input[m:]

    return Groups

def Decrement(A, i):
    if len(A[i]) == 0:
        # Borrow
        Decrement(A, i - 1)
        A[i]= Divided[i - 1] + A[i]
    A[i]= A[i][:-1]

def Subtract(A, B):
    for i in range(len(Digits) - 1, -1, -1):
        while len(B[i]) > 0:
            Decrement(A, i)
            B[i]= B[i][:-1]

def Out(Input):
    return Input.replace("DCCCC", "CM").replace("CCCC", "CD").replace("LXXXX", "XC").replace("XXXX", "XL").replace("VIIII", "IX").replace("IIII", "IV")

A= Group(In("MMDCVI"))
B= Group(In("CCCXLIII"))
Subtract(A, B)
print Out("".join(A))

>>> 
MMCCLXIII

答案 6 :(得分:-1)

Enum怎么样?

public enum RomanNumber
{
I = 1,
II = 2,
III = 3,
IV = 4,
V = 5,
VI = 6,
VII = 7,
VIII = 8,
IX = 9
X = 10
}

然后像这样使用它:

  

int newRomanNumber =(int)RomanNumber.X - (int)RomanNumber.III

如果您的输入是'X - III = VII',那么您还必须解析此字符串。 但我不会为你做这项工作。 ; - )