0

Is possible to subtract roman numbers without conversion to decimal numbers?

For Example:

X - III = VII

So in input I have X and III. In output I have VII.

I need algorithm without conversion to decimal number.

Now I don't have an idea.

Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
Norbert Pisz
  • 3,392
  • 3
  • 27
  • 42
  • 3
    Is it a homework assignment since you don't want to convert it through decimal? Because it would be the easiest. (and if so - a cheating could be to run it past binary :o :D) – Allan S. Hansen Feb 26 '14 at 11:39
  • 4
    You can convert to unary using Roman Numeral notation, do the maths, then renormalize. I.e. you're performing `IIIIIIIIII - III` to get the result `IIIIIII` and then you just have to normalize that back to conventional roman numerals. – Damien_The_Unbeliever Feb 26 '14 at 11:43
  • With conversion it can take 5 minutes. But I need solution without conversion :) – Norbert Pisz Feb 26 '14 at 11:43
  • Then you should do what Damien suggest. @Damien_The_Unbeliever. You should add that as an answer – Allan S. Hansen Feb 26 '14 at 11:45
  • @Damien_The_Unbeliever but it will be hard to convert IIIIII to normal ROman number :) – Norbert Pisz Feb 26 '14 at 11:46
  • Do you also need to support M, D, and C? Then you should really go for an enum. :) – Fabian Bigler Feb 26 '14 at 11:48
  • @FabianBigler I can't use your solution becouse contains conversion :) – Norbert Pisz Feb 26 '14 at 11:49
  • I think I can see a way to follow through with my (only semi serious) comment by using iterators or observables. But it's definitely falling into programming puzzle territory at that point and stops looking on topic for SO. – Damien_The_Unbeliever Feb 26 '14 at 12:17
  • @Norbert Pisz: I would improve the conversion instead of thinking about an obfuscated algorithm to subtract roman numbers directly. I doubt the computation without conversion will be any faster. – MrSmith42 Feb 26 '14 at 13:06

7 Answers7

2

The most simple algorithm will be to create -- function for Romans. Subtracting A-B means repeating simultaneous A-- and B--, until having nothing in B.


But I wanted to do something more effective

The Roman numbers can be looked at as positional in some very weak way. We'll use it.

Let's make short tables of substraction:

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=_

And addition:

V+I=VI

And the same for CLX and MDC levels. Of course, you could create only one table, but to use it on different levels by substitution of letters.

  • Let's take numbers, for example, A=MMDCVI=2606 a B=CCCXLIII=343
  • Lets distribute them into levels=powers of 10. The several following operations will be inside levels only.

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

  • Then subtracting

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

  • At the every level we have three possible letter: units, five-units and ten-units. The combinations (unit, five-units) and (unit, ten-unit) will be translated into differences

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

  • The normal combinations (where senior symbol is before junior one), will be translated into sums.

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

  • Shorten the combinations of same symbols

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

  • If some level is negative, borrow a unit from the senior level. Of course, it could work through empty level.

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

  • Now, in every level we'll apply the subtraction table we have made, subtracting every minused symbol, strarting from the top of the table and repeating it until no minused members remain.

    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)

  • Now, use the addition table

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

  • Now, we'll open the parenthesis. If there is '_' in some level, there will be nothing on its place.

    A-B=MMCCLXIII =2263

The result is correct.

Gangnus
  • 24,044
  • 16
  • 90
  • 149
1

There is a more elegant solution than simply unrolling the whole roman number. The disadvantage of this would be a complexity in O(n) as opposed to O(log n) where n is the input number.

I found this task quite interesting. It is indeed possible without a conversion. Basically, you just have look at the last digit. If they match, take them away, if not, replace the bigger one. However, the whole task gets a lot more complicated by numbers like "IV", because you need a lookahead.

Here is the code. Since this is most likely a homework assignment, I took out some code so you have to think for yourself, how the rest should look like.

    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
    }
Georg
  • 5,626
  • 1
  • 23
  • 44
0

Apart from checking every possible combination of input numbers - assuming the input is bounded - there is no way to do what you're asking. Roman numerals are awful in terms of mathematical operations.

You could write an algorithm that doesn't convert them, but it'd have to use decimal numbers at some point. Or you could normalize them to e.g. "IIIII...", but again you'd need to write some equivalences like "50 chars = L".

Solal Pirelli
  • 1,199
  • 9
  • 21
0

Rough idea:

Create a "map" or list of how each roman numeral relates to simpler numerals, for instance IV corresponds to (II + II), while V corresponds to (III + II), and X corresponds to (V + V).

When calculating e.g. X - III, treat this not as a mathematical term, but a string, which can be changed in several steps, where you each time check for something to remove from both sides of the minus operator:

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. 

If you make each number correspond to something as simple as possible in the "map" (e.g. III can correspond to (II + I), so you don't get stuck with left-overs), then I'm pretty sure you can figure out some kind of solution here.

Of course this requires a bunch of string-operations, comparisons, and a map from which your algorithm can "know" how to compare or switch values. Not exactly traditional maths, but then again, I suppose this is how roman numerals do work.

Kjartan
  • 18,591
  • 15
  • 71
  • 96
0

The basic sketch of my idea is to build up simple converters that chain together via either iterators or observables.

So, for instance, on the input side of things you have a CConverter that performs the transormations of the combinations CD, CM, D and M into CCCC, CCCCCCCCC, CCCCC, and CCCCCCCCCC respectively. All other received inputs are passed through unmolested. Then the next converter in line XConverter converts XL, XC, L and X into the appropriate number of Xs, and so on until you just have a stream of all Is.

Then you perform the subtraction by consuming both of these streams of Is, in lockstep. If the minuend runs out first, then the answer is 0 or negative, in which case everything has gone wrong. Otherwise, when the subtrahend runs out, you just start emitting all remaining Is from the minuend.

Now you need to convert back. So the first INormalizer queues up Is until it's received five of them, then it emits a V. If it reaches the end of the stream and it received four, then it emits IV. Otherwise it just emits as many Is as it received until the end of the stream, and then ends its own stream.

Next, the VNormalizer queues up Vs until it's received two, and then emits an X. If it receives an IV and it has one queued V then it emits IX, otherwise it emits IV.

And if the stream it's receiving ends or just starts sending Is and it still has a V queued, then it emits that, then whatever else the sending stream wanted to send, and then ends its own stream.

And so on, building back up into the correct roman numerals.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
0

Parse the input strings to group the digits in the mixed 5/10 base (M, D, C, L, X, I). I.e. MMXVII yields MM||||X|V|II.

Now subtract from right to left, by canceling the digits in pairs. I.e. V|III - II = V|II - I = V|I.

When required, do a borrow, i.e. split the next highest digit (V splits to IIIII, X to VV...). Example: V|I - III = V| - II = IIIII - II = III. Borrows may need to be recursive, like X||I - III = X|| - II = VV| - II = V|IIIII - II = V|III.

The prefix notation (IV, IX, XL, XC...) makes it a little more complicated. An approach is to preprocess the string to remove them on input (substitute with IIII, VIIII, XXXX, LXXXX...) and postprocess to restore them on output.

Example:

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

Pure character processing, no arithmetic involved.

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
-1

How about an Enum?

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

Then using it like this:

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

If your input is 'X - III = VII', then you will also have to parse this string. But I won't do this work for you. ;-)

Fabian Bigler
  • 10,403
  • 6
  • 47
  • 70