繰り返したくない場合は、コードゴルフ新年版 - 整数からローマ数字への 貢献からインスピレーションを得ることができます。
-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].
erlang でコードを書くのは良くないし、推奨される方法でもありません。マクロはダメです。できれば避けてください。デバッグが難しく、ホット コード スワップによって追跡されないモジュール間の依存関係が導入されます。「コードはデータ、データはコード」というより機能的なアプローチが好きな場合は、これを例として見てください。
-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.
念のために言っておきますが、あなたのコードは私のコードよりもバイトコードで約 5% 速く、ネイティブで 5% 遅いです。Intel(R) Core(TM)2 Duo CPU T7500 @ 2.20GHz で、バイトコードでは 1.2us、ネイティブでは 370ns で 1 回の変換を実行します。
編集:再帰の深さが非常に小さいため、末尾再帰バージョンは使用していません。そのため、パフォーマンスの低下やメリットがあるかどうかに興味がありました。ネイティブでもバイトコードで私のアルゴリズムを測定することはできませんが、興味深いことが元のコードで起こります。元のアルゴリズムを単純な方法 (テール コール用に最適化されていない) で記述した場合、ネイティブ コードの場合よりも約 40% 高速です (約 250 ns で 1 つの変換)。バイトコードに測定可能な違いはありません。テール コールの最適化を行う価値がない興味深い例です。
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) -> [].
PS : リストのフラット化は、Erlang コードでは一般的な動作ではありません。上記の例のリターン構造はよく知られてio_list
おり、通常は erlang io システムで受け入れられています。ソケット、ポートなどに直接送信できます。たとえば、それを書きたい場合は、io:put_chars(IOList)
またはを使用できますio:format("Result: '~s'~n", [IOList])
。
EDIT2:演算子erlangコンパイラの左オペランドとして定数リストがある場合、++
リスト連結を最適化["string" | L]
するため、速度には必要ありません。結果のコードはより読みやすくなり、結果はパフォーマンスの低下なしに平坦化されます。個人的には、パフォーマンスに興味がある場合は、このバージョンを使用します。これは少し反復的ですが、私が知っている中で最速であり、バイトコードで 310ns、ネイティブで 210ns で 1 つの変換を実行します。
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".