soxfox

This week of #48in24, we’ll be looking at at Roman Numerals. As you might be able to guess from the exercise name, the goal is to create a function that takes an integer, and returns a string with its Roman numeral representation.

For this one, I won’t be showcasing anything particularly exciting, and I’ll just let the featured languages speak for themselves.

The general algorithm I’m implementing in all three languages is as follows:

  1. Create a list of value to Roman numeral pairs (including 4s and 9s).
  2. Create a blank string to hold the result.
  3. For each pair in the list:
    1. While the target number is greater than or equal to the value:
      1. Subtract the value from the target number.
      2. Add the Roman numeral fragment to the result string.
  4. Return the result.

Languages

Elixir

Elixir is a functional programming language built on the Erlang VM. As it’s functional, there is a lot of focus on features like pattern matching, function pipelining, and recursive algorithms. It’s not a purely functional language though, so side-effects are possible without too much hassle.

My Roman Numerals implementation in Elixir uses all three of the features I mentioned above, and looks like this:

defmodule RomanNumerals do
  def numeral(0), do: ""
  def numeral(number) do
    {value, roman} = [
      {1000, "M"},
      {900, "CM"}, {500, "D"}, {400, "CD"}, {100, "C"},
      {90, "XC"}, {50, "L"}, {40, "XL"}, {10, "X"},
      {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"},
    ] |> Enum.find(fn {n, _} -> number >= n end)
    roman <> numeral(number - value)
  end
end

Pattern matching is used in a couple of ways here. First, it is used in one of the numeral definitions to match the literal value 0. When asked to convert 0 to Roman numerals, this code just returns an empty string. This isn’t strictly correct, but it works well for this recursive implementation. Pattern matching is used again to split the {value, roman} tuples, both in the Enum.find call and in the top level assignment.

Function pipelining isn’t used much here, but it cleans up the code that selects a pairing by avoiding nesting of the entire array into the Enum.find call. You can probably see how this would be useful for longer data processing flows. Arbitrary functions can be chained together as long as you can pass data into their first argument - though it’s almost always possible to adapt functions to your needs with Elixir’s excellent anonymous function shorthand.

Finally, recursion causes the biggest shift from the algorithm I outlined at the start of this post. Rather than explicitly using two nested loops, I just find the biggest value, process that one, then hand it off to the next recursive call to do the rest. Each roman fragment is concatenated with the result of the next call, building a complete Roman numeral at the end.

Pharo

Pharo is one of the weirder languages supported by Exercism. Like almost all Smalltalk derivatives, it includes a complete graphical interface for development, and everything takes place within a live environment.

My solution in Pharo follows the pseudocode pretty closely, but there are some interesting syntax points to look at. The following function lives inside a class called RomanNumerals, by the way.

romanNumber: aNumber
	| numerals remaining result |
	numerals := {
		1000 -> 'M'.
		900 -> 'CM'. 500 -> 'D'. 400 -> 'CD'. 100 -> 'C'.
		90 -> 'XC'. 50 -> 'L'. 40 -> 'XL'. 10 -> 'X'.
		9 -> 'IX'. 5 -> 'V'. 4 -> 'IV'. 1 -> 'I'. }.
	remaining := aNumber.
	result := String new writeStream.
	numerals do: [ :pair |
		[ remaining >= pair key ] whileTrue: [ 
			remaining := (remaining - pair key).
			result << pair value ] ].
	^ result contents
  1. Create a list of value to Roman numeral pairs (including 4s and 9s).

    For this, I used a dynamic list literal containing some Associations. These are pairs designed for use as key-value records, and are often used to initialise Dictionarys and other such collections.

  2. Create a blank string to hold the result.

    String new makes a string literal, but I then created a stream from that for more efficient string building.

  3. For each pair in the list:

    This is done with the do: message, which takes a block (basically an anonymous function/closure), and runs it once for each list item.

    1. While the target number is greater than or equal to the value:

      Similar to the last point, this uses blocks, both for the condition and the body of the loop. The condition needs to be checked multiple times, so I can’t just use a static value.

      1. Subtract the value from the target number.

      2. Add the Roman numeral fragment to the result string.

        These are pretty straightforward.

  4. Return the result.

    Smalltalk uses ^ to indicate the return value, and returns the object itself (self) by default.

Julia

The final language this week is Julia, which is much like last week’s R, is a language targeted at data analysis. Julia is newer, and feels a little closer to the style of modern programming languages. I’ve used it before, mainly with Pluto which is a very nice interactive notebook tool.

There is one small difference in the Julia version of the Roman Numerals exercise: it expects the code to throw an error when given a value less than 1.

function to_roman(number)
    if number <= 0 error("invalid number") end
    numerals = [
        (1000, "M"),
        (900, "CM"), (500, "D"), (400, "CD"), (100, "C"),
        (90, "XC"), (50, "L"), (40, "XL"), (10, "X"),
        (9, "IX"), (5, "V"), (4, "IV"), (1, "I"),
    ]
    result = ""
    for (value, roman) in numerals
        while number >= value
            number -= value
            result *= roman
        end
    end
    result
end

This code really closely follows the pseudocode version, so here are a few interesting points I picked up on:

Final Thoughts

I kept this week pretty simple, in part because these are languages I haven’t used much. I’ll probably continue to use Julia in Pluto.jl occasionally, but I’m not sure I have much need for Elixir or Pharo beyond curiosity. Pharo is another language which has relatively little syntax explicitly defined, but that allows a huge amount of flexibility, like the Lisps I’ve looked at previously, so that was fun to play with.