[48in24 Exercise] [02-06] Roman Numerals

This issue is a discussion for contributors to collaborate in getting ready to be featured in 48in24. Please refer to this forum topic for more info.

We will be featuring Roman Numerals from Feb 06 onwards.

Staff jobs

These are things for Erik/Jeremy to do:

  • :ballot_box_with_check: Check/update exercise in Problem Specifications
  • :ballot_box_with_check: Create + schedule video

Community jobs

For each track:

  • Implement Roman Numerals
  • Add approaches (and an approaches introduction!) for each idiomatic or interesting/educational approach.
  • Add video walkthroughs (record yourself solving and digging deeper into the exercise).
  • Highlight up to 16 different featured exercises (coming soon)

Existing Approaches

You can use these as the basis for approaches on your own tracks. Feel free to copy/paste/reuse/rewrite/etc as you see fit! Maybe ask ChatGPT to translate to your programming language.

Track Statuses

You can see an overview of which tracks have implemented the exercise at the #48in24 implementation status page.

Surprisingly (IMO) PL/SQL has support for converting numbers to Roman numerals builtin

TO_CHAR(num, 'RN')
1 Like

V implementation PR

I’ll port it for CFML and CoffeeScript.

1 Like

R has a built-in too apparently at R: Roman Numerals.

I didn’t use this in creating the exercise (PR #311), but it’s certainly something I’ll mention in the approaches docs when I write them.

I’m currently writing Python approaches for Roman Numerals, then R will follow.

1 Like

I belatedly checked, and this is also available in Python.

It needs installation of a separate module, so I’m not yet sure if it’s available in the test runner (I’ll find out). If not, that’s probably a good thing!

Unless its part of the standard lib, its not available in the test runner for Python (and won’t be). :smile:

I do have a small list of candidate libs we may add in the (far) future, but we have to test them to make sure they don’t bloat the runner.

1 Like

For fans of obfuscated code, I bring you a couple of Python examples from past submissions. They pass all tests (except good taste?)

From youlifil:

def roman(number):
    return ''.join(one*digit if digit<4 else one+five if digit==4 else five+one*(digit-5) if digit<9 else one+ten
        for digit, (one,five,ten)
        in zip([int(d) for d in str(number)], ["--MDCLXVI"[-i*2-1:-i*2-4:-1] for i in range(len(str(number))-1,-1,-1)]))

From MAPKarrenbelt:

from itertools import starmap

def roman(number: int) -> str:
    orders = [(1000, "M  "), (100, "CDM"), (10, "XLC"), (1, "IVX")] 
    options = lambda I, V, X: ["", I, I * 2, I * 3, I + V, V, V + I, V + I * 2, V + I * 3, I + X]
    compute = lambda n, chars: options(*chars)[number % (n * 10) // n]
    return "".join(starmap(compute, orders))

As you would guess, PEP-8 linters complain about both.

The latter looks a bit like how I solved it. But I think the linter might be fine with mine.

# The 10's, 5's and 1's position chars for 1, 10, 100, 1000.
DIGIT_CHARS = ["XVI", "CLX", "MDC", "??M"]

def roman(number: int) -> str:
    """Return the Roman numeral for a number."""
    # Generate a mapping from numeric value to Roman numeral.
    mapping = []
    for position in range(len(DIGIT_CHARS) - 1, -1, -1):
        # Values: 1000, 100, 10, 1
        scale = 10 ** position
        chars = DIGIT_CHARS[position]
        # This might be: (9, IX) or (90, XC)
        mapping.append((9 * scale, chars[2] + chars[0]))
        # This might be: (5, V) or (50, D)
        mapping.append((5 * scale, chars[1]))
        # This might be: (4, IV) or (40, XD)
        mapping.append((4 * scale, chars[2] + chars[1]))
        mapping.append((1 * scale, chars[2]))

    out = ""
    for num, numerals in mapping:
        while number >= num:
            out += numerals
            number -= num
    return out
1 Like

I don’t know if this is considered an unusual and/or interesting approach, but here it is:

def roman(number):
    if number >= 900 and number < 1000 or number >= 400 and number < 500:
        return 'C' + roman(number + 100)
    elif number >= 90 and number < 100 or number >= 40 and number < 50:
        return 'X' + roman(number + 10)
    elif number == 9 or number == 4:
        return 'I' + roman(number + 1)
    elif number >= 1000:
        return 'M' + roman(number - 1000)
    elif number >= 500:
        return 'D' + roman(number - 500)
    elif number >= 100:
        return 'C' + roman(number - 100)
    elif number >= 50:
        return 'L' + roman(number - 50)
    elif number >= 10:
        return 'X' + roman(number - 10)
    elif number >= 5:
        return 'V' + roman(number - 5)
    elif number >= 1:
        return 'I' + roman(number - 1)
        return ''

If the conversion is about to use a subractive form (for example, IX for 9), the above program appends the subracted numeral (I) to the output string, and then goes on converting the number incremented by the subracted amount (+ 1).

This results in a recursive process which oscillates between larger and smaller numbers to convert.

My example solution is similar in adding one and making a recursive call.

Instead of listing all the cases, I check if digit % 5 == 4

I’m still looking for some nice, weird or just generally interesting solutions. Bonus points if they’re not written in Python (we like to feature different languages).

Nice and similar to make of the above, but in awk by Glenn: glennj's solution for Roman Numerals in AWK on Exercism

Not sure if mine fits the bill :see_no_evil:

and Common Lisp

(defun romanize (number)
  (format nil "~@R" number))
1 Like

I found this out while looking for solutions :)


I submitted two solutions using the above increment/decrement recursive approach (which I call a “Collatz-like” approach) in Elixir and C:

1 Like