If you want not be repetitive, you can inspire by mine Code Golf New Year Edition - Integer to Roman Numeral contribution.
-module(n2).
-export([y/1]).
-define(D(V,S),n(N)when N>=V->[??S|n(N-V)];).
y(N)->io:format(n(N)).
?D(1000,M)?D(900,CM)?D(500,D)?D(400,CD)?D(100,C)?D(90,XC)?D(50,L)?D(40,XL)?D(10,X)?D(9,IX)?D(5,V)?D(4,IV)?D(1,I)n(0)->[10].
It is not nice and recommended way to write code in erlang. Macros are bad. If you can, avoid it. It is hard to debug, it introduces intermodule dependencies which are not tracked by hot code swap, and so and so. If you like more functional like approach "code is data, data is code" look at this as example:
-module(roman).
-compile([export_all]).
toRoman(N) when is_integer(N), N >= 0 ->
toRoman(N,
[{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"}]).
toRoman(0, _) -> [];
toRoman(N, [{X, V} | _] = S) when N >= X ->
[V | toRoman(N - X, S)];
toRoman(N, [_ | S]) -> toRoman(N, S).
test() ->
F = fun (X) -> lists:flatten(toRoman(X)) end,
"" = F(0),
"I" = F(1),
"III" = F(3),
"VI" = F(6),
"XXIII" = F(23),
"XLIII" = F(43),
"LXXV" = F(75),
"LXXXVII" = F(87),
"XIII" = F(13),
"XXIII" = F(23),
"MMMCMXCIX" = F(3999),
"MMMCMXCVIII" = F(3998),
"MMDXXXI" = F(2531),
"CXL" = F(140),
ok.
Just for curiosity, your code is about 5% faster in bytecode and 5% slower in native than mine. It performs one translation in 1.2us in bytecode and in 370ns in native on Intel(R) Core(TM)2 Duo CPU T7500 @ 2.20GHz.
Edit: I have not used tail recursive version because depth of recursion is very small. So I was curious if there is any performance penalty or gain from it. I can't measure any in mine algorithm in bytecode even native but interesting thing happen in original code. If I wrote original algorithm in straight forward way (not optimized for tail call) it is about 40% faster than mine in native code (one transformation in approx 250ns). There is not measurable difference in byte code. It is interesting example where tail call optimization is not worth to do.
toRoman(N) when N >= 1000 -> "M" ++ toRoman(N - 1000);
toRoman(N) when N >= 900 -> "CM" ++ toRoman(N - 900);
toRoman(N) when N >= 500 -> "D" ++ toRoman(N - 500);
toRoman(N) when N >= 400 -> "CD" ++ toRoman(N - 400);
toRoman(N) when N >= 100 -> "C" ++ toRoman(N - 100);
toRoman(N) when N >= 90 -> "XC" ++ toRoman(N - 90);
toRoman(N) when N >= 50 -> "L" ++ toRoman(N - 50);
toRoman(N) when N >= 40 -> "XL" ++ toRoman(N - 40);
toRoman(N) when N >= 10 -> "X" ++ toRoman(N - 10);
toRoman(N) when N >= 9 -> "IX" ++ toRoman(N - 9);
toRoman(N) when N >= 5 -> "V" ++ toRoman(N - 5);
toRoman(N) when N >= 4 -> "IV" ++ toRoman(N - 4);
toRoman(N) when N >= 1 -> "I" ++ toRoman(N - 1);
toRoman(0) -> [].
P.S.: Flattening lists is not common behavior for Erlang code. Return structure in above examples is well known as io_list
and is usually accepted in erlang io system. You can send it directly to sockets, ports and so. If you want for example write it you can use io:put_chars(IOList)
or io:format("Result: '~s'~n", [IOList])
.
EDIT2: If there is constant list as left operand of ++
operator erlang compiler will optimize list concatenation for you thus ["string" | L]
is not not necessary for speed. Resulting code is more readable and result is flattened without performance penalty. Personaly if I would be interested in performace I would use this version which is little bit repetitive but is the fastest what I know and performs one transformation in 310ns in byte code and in 210ns in native.
toRoman(N) when N >= 1000 -> "M" ++ toRoman(N - 1000);
toRoman(N) -> toRomanC(N div 100, N rem 100).
toRomanC(0, N) -> toRomanX(N);
toRomanC(1, N) -> "C" ++ toRomanX(N);
toRomanC(2, N) -> "CC" ++ toRomanX(N);
toRomanC(3, N) -> "CCC" ++ toRomanX(N);
toRomanC(4, N) -> "CD" ++ toRomanX(N);
toRomanC(5, N) -> "D" ++ toRomanX(N);
toRomanC(6, N) -> "DC" ++ toRomanX(N);
toRomanC(7, N) -> "DCC" ++ toRomanX(N);
toRomanC(8, N) -> "DCCC" ++ toRomanX(N);
toRomanC(9, N) -> "CM" ++ toRomanX(N).
toRomanX(N) -> toRomanX(N div 10, N rem 10).
toRomanX(0, N) -> toRomanI(N);
toRomanX(1, N) -> "X" ++ toRomanI(N);
toRomanX(2, N) -> "XX" ++ toRomanI(N);
toRomanX(3, N) -> "XXX" ++ toRomanI(N);
toRomanX(4, N) -> "XL" ++ toRomanI(N);
toRomanX(5, N) -> "L" ++ toRomanI(N);
toRomanX(6, N) -> "LX" ++ toRomanI(N);
toRomanX(7, N) -> "LXX" ++ toRomanI(N);
toRomanX(8, N) -> "LXXX" ++ toRomanI(N);
toRomanX(9, N) -> "XC" ++ toRomanI(N).
toRomanI(0) -> [];
toRomanI(1) -> "I";
toRomanI(2) -> "II";
toRomanI(3) -> "III";
toRomanI(4) -> "IV";
toRomanI(5) -> "V";
toRomanI(6) -> "VI";
toRomanI(7) -> "VII";
toRomanI(8) -> "VIII";
toRomanI(9) -> "IX".