11.2 Solution

Let’s start with a simple mapping between some key Roman numerals and their Arabic counterparts.

const roman2arabic = [("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)]

The mapping is defined with (const) keyword to signal that we do not wish to change it throughout the program execution. Moreover, we used a vector of tuples, not a dictionary, since we want to preserve the (descending) order of the pairs of values. Notice, that we also incluced the key landmarks of subtractive notation (e.g. ("CM", 900) or ("IV", 4)). Now, we are ready to take the next step.

function getRoman(arabic::Int)::Str
    @assert 0 < arabic < 4000
    roman::Str = ""
    for (r, a) in roman2arabic
        while(arabic >= a)
            roman *= r
            arabic -= a
        end
    end
    return roman
end

We will build our Roman numeral bit by bit starting from an empty string (roman::Str = ""). For that we traverse all our Roman and Arabic landmarks (for (r, a) in roman2arabic). For each of them (starting from the highest number), we check if the currently examined Arabic landmark (a) is lower than the Arabic number we got to translate (arabic). As long as it is (while(arabic >= a)) we append the parallel Roman landmark (r) to our solution (roman *= r) and subtract the Arabic landmark (a) from our Arabic number (arabic -= a). Once we are done we return the result.

Note. To better understand the above code you may read about the while loop in the docs and use show macro to have a sneak peak how the variables in the loop change.

Time for a minitest (go ahead pick a number and, in your head or on a piece of paper, follow the function’s execution).

getRoman.(1:10)
["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"]

OK, time for a bigger test.

getRoman.(arabicTest) == romanTest

true

Looks alright.

Time to write our getArabic function. For that we will have to break a Roman numeral into tokens (from left to right) that we will use to build up an Arabic number.

const romanTokens = map(first, roman2arabic)
# equivalent to
const romanTokens = map(tuple -> tuple[1], roman2arabic)

function getTokenAndRest(roman::Str)::Tuple{Str, Str}
    if length(roman) <= 1
        return (roman, "")
    elseif roman[1:2] in romanTokens
        return (roman[1:2], string(roman[3:end]))
    else
        return (string(roman[1]), string(roman[2:end]))
    end
end

function getTokens(roman::Str)::Vec{Str}
    curToken::Str = ""
    allTokens::Vec{Str} = []
    while (roman != "")
        curToken, roman = getTokenAndRest(roman)
        push!(allTokens, curToken)
    end
    return allTokens
end

First, we extract romanTokens with map and first. Next, we use getTokenAndRest to split a Roman numeral into a key token (first on the left, either one or two characters long) and the rest of the numeral. The string(sth) part makes sure we always return a string and not a character. Anyway, based on it (getTokenAndRest) we split a Roman numeral into a vector of tokens with getTokens. Now, we are ready to write our getArabic.

function getVal(lookup::Vec{Tuple{Str, Int}}, key::Str, default::Int)::Int
    for (k, v) in lookup
        if k == key
            return v
        end
    end
    return default
end

function getArabic(roman::Str)::Int
    tokens::Vec{Str} = getTokens(roman)
    sum::Int = 0
    for curToken in tokens
        sum += getVal(roman2arabic, curToken, 0)
    end
    return sum
end

Notice, that before we moved to getArabic we wrote getVal that will provide us with an Arabic number for a given Roman numeral token. The above was not strictly necessary, as we could have just use the built-in get for that purpose (e.g. with get(Dict(roman2arabic), key, default)). Anyway, to get the Arabic number for a given Roman numeral (roman), first we split it into a vector of tokens and traverse them one by one (curToken) with the for loop. We translate curToken to an Arabic numeral and add it to sum which we return once we are done.

Again, let’s go with a minitest (go ahead pick a number and, in your head or on a piece of paper, follow the function’s execution).

getArabic.(["I", "II", "III", "IV", "V", "VI", "VII", "VIII"])
[1, 2, 3, 4, 5, 6, 7, 8]

Finally, a bigger test.

getArabic.(romanTest) == arabicTest

true

And another one.

getArabic.(getRoman.(arabicTest)) == arabicTest

true

I don’t know about you, but to me the results are satisfactory.



CC BY-NC-SA 4.0 Bartlomiej Lukaszuk