代码之家  ›  专栏  ›  技术社区  ›  Adam Bard

帮我改进这个Erlang?

  •  4
  • Adam Bard  · 技术社区  · 15 年前

    所以我对Erlang很感兴趣。我找不到借口用它来做任何大事,但我会时不时地用它来解决玩具问题。

    -module(roman).
    -compile([export_all]).
    
    toRoman(N) ->
        toRoman(N, []).
    
    toRoman(0,Acc) ->
        lists:reverse(lists:flatten(Acc));
    
    toRoman(N, Acc) when N >= 1000 ->
        toRoman(N-1000,["M"|Acc]);
    
    toRoman(N,Acc) when N >= 900 ->
        toRoman(N-900,["CM" | Acc]);
    
    toRoman(N,Acc) when N >= 500 ->
        toRoman(N-500,["D" | Acc]);
    
    toRoman(N,Acc) when N >= 400 ->
        toRoman(N-400, ["CD" | Acc]);
    
    toRoman(N,Acc) when N >= 100 ->
        toRoman(N-100, ["C" | Acc]);
    
    toRoman(N,Acc) when N >= 90 ->
        toRoman(N-90, ["XC" | Acc]);
    
    toRoman(N,Acc) when N >= 50 ->
        toRoman(N-50, ["L" | Acc]);
    
    toRoman(N, Acc) when N >= 40 ->
        toRoman(N-40, ["XL" | Acc]);
    
    toRoman(N, Acc) when N >= 10 ->
        toRoman(N-10, ["X" | Acc]);
    
    toRoman(N, Acc) when N >= 9 ->
        toRoman(N-9, ["IX" | Acc]);
    
    toRoman(N, Acc) when N >= 5 ->
        toRoman(N-5, ["V" | Acc]);
    
    toRoman(N, Acc) when N >= 4 ->
        toRoman(N-4, ["IV" | Acc]);
    
    toRoman(N, Acc) ->
        toRoman(N-1, ["I" | Acc]).
    
    test() ->
        Test = fun(X) -> io:format("~p -> ~p~n", [X, toRoman(X)]) end,
        lists:map(Test, [0,1,3,6,23,43,75,87,13,23, 3999, 3998, 2531, 140]).
    

    我觉得有一个更好的方法。有人能提供一些见解吗?

    8 回复  |  直到 13 年前
        1
  •  4
  •   Community Tales Farias    7 年前

    如果你不想重复,你可以从我的作品中得到启发 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].
    

    -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本机执行一次转换。

    编辑 :我没有使用尾部递归版本,因为递归的深度非常小。所以我很好奇是否有任何表现上的损失或从中获益。我无法用字节码衡量我的算法中的任何内容,即使是原生的,但有趣的事情发生在原始代码中。如果我以直接的方式编写原始算法(不针对尾部调用进行优化),它比我在本机代码中的速度快40%左右(一次转换大约需要250ns)。字节码之间没有可测量的差异。这是一个有趣的例子,尾部调用优化不值得做。

    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) -> [].
    

    附笔。 :展平列表不是Erlang代码的常见行为。上述示例中的返回结构众所周知为 io_list 并且在erlang io系统中通常被接受。您可以直接将其发送到套接字、端口等。例如,如果你想写,你可以使用 io:put_chars(IOList) io:format("Result: '~s'~n", [IOList]) .

    编辑2 ++ 运算符erlang编译器将为您优化列表连接 ["string" | L]

    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".
    
        2
  •  6
  •   Jeremy Wall    15 年前

        3
  •  5
  •   I GIVE TERRIBLE ADVICE    15 年前

    我已经把这个添加到 rosettacode.org 之前,在这里重新发布。

    -module(roman).
    -export([to_roman/1]).
    
    to_roman(0) -> [];
    to_roman(X) when X >= 1000 -> "M" ++ to_roman(X-1000);
    to_roman(X) when X >= 100 -> digit(X div 100, $C,$D,$M) ++ to_roman(X rem 100);
    to_roman(X) when X >= 10 -> digit(X div 10, $X,$L,$C) ++ to_roman(X rem 10);
    to_roman(X) when X >= 1 -> digit(X, $I,$V,$X).
    
    digit(1,X,_,_) -> [X];
    digit(2,X,_,_) -> [X,X];
    digit(3,X,_,_) -> [X,X,X];
    digit(4,X,Y,_) -> [X,Y];
    digit(5,_,Y,_) -> [Y];
    digit(6,X,Y,_) -> [Y,X];
    digit(7,X,Y,_) -> [Y,X,X];
    digit(8,X,Y,_) -> [Y,X,X,X];
    digit(9,X,_,Z) -> [X,Z].
    
        4
  •  2
  •   Christian    15 年前

    重复部分是累加和函数调用。把它们搬到一个地方,事情就会好得多。

    %%% Roman numerals ruleset
    r(N) when N >= 1000 -> {N-1000, "M"};
    r(N) when N >= 900 -> {N-900, "CM"};
    r(N) when N >= 500 -> {N-500, "D"};
    r(N) when N >= 400 -> {N-400, "CD"};
    r(N) when N >= 100 -> {N-100, "C"};
    r(N) when N >= 90 -> {N-90, "XC"};
    r(N) when N >= 50 -> {N-50, "L"};
    r(N) when N >= 40 -> {N-40, "XL"};
    r(N) when N >= 10 -> {N-10, "X"};
    r(N) when N >= 9 -> {N-9, "IX"};
    r(N) when N >= 5 -> {N-5, "V"};
    r(N) when N >= 4 -> {N-4, "IV"};
    r(N) when N > 0 -> {N-1, "I"}.
    
    roman(N, Acc) ->
      case r(N) of
        {0, R} ->
          [R | Acc];
        {N2, R} ->
          roman(N2, [R | Acc])
      end.
    
    roman(N) ->
      list_to_binary(lists:reverse(roman(N, ""))).
    

    8> [roman:toRoman(N) || N <- lists:seq(1,10)].   
    ["I","II","III","VI","V","VI","VII","VIII","XI","X"]
    

    同样的错误告诉你9和11是相等的,40和60,90和110。。。。

        5
  •  2
  •   cthulahoops    15 年前

    这个过程有三个部分,一个规则列表,其中哪些符号代表哪些数字,搜索这些规则以找到下一个符号,以及一个将数字减少到零的迭代。每个部分都有一个函数,我们有:

    ruleset() -> [
        {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"}].
    
    find_next(N) -> find_next(ruleset(), N).
    
    find_next([{V, Symbol}|_], N) when N >= V -> {N - V, Symbol};
    find_next([_|T], N) -> find_next(T, N).
    
    roman(N, Acc) ->
        case find_next(N) of
              {0, R}  -> [R | Acc];
              {N2, R} -> roman(N2, [R | Acc])
        end.
    
    roman(N) ->
        lists:append(lists:reverse(roman(N, ""))).
    

        6
  •  1
  •   Zed    15 年前

    -module(roman).
    -compile([export_all]).
    
    to_roman(N) when N >= 1000 -> "M"  ++ to_roman(N-1000);
    to_roman(N) when N >=  900 -> "CM" ++ to_roman(N- 900);
    ...
    to_roman(N) when N >=    4 -> "IV" ++ to_roman(N-   4);
    to_roman(N) when N >=    1 -> "I"  ++ to_roman(N-   1);
    to_roman(_) -> [].
    

    通过定义宏,可以进一步保存一些字符。我确实讨厌宏,但你可能会喜欢它们:)。

    -module(roman).
    -compile([export_all]).
    
    -define( TO_ROMAN(L, C) , to_roman(N) when N >= L -> C ++ to_roman(N-L) ).
    
    ?TO_ROMAN(1000,  "M");
    ?TO_ROMAN( 900, "CM");
    ...
    ?TO_ROMAN(   4, "IV");
    ?TO_ROMAN(   1,  "I");
    to_roman(_) -> [].
    
        7
  •  1
  •   Gordon Guthrie    15 年前

    in Excel 但基本上,您的代码仍然是一系列大小写/模式匹配头。。。

        8
  •  -1
  •   obecalp    15 年前

    在任何语言中,使用查找表都应该使其更短更快。