6

Archaelusはこの投稿で、名前付きパラメーターを処理するための新しいフォーマットルーチンを作成することは良い学習演習になる可能性があることを示唆しました。そこで、言語を学ぶという精神で、名前付きパラメーターを処理するフォーマットルーチンを作成しました。



例:

1> fout:format("hello ~s{name}, ~p{one}, ~p{two}, ~p{three}~n",[{one,1},{three,3},{name,"Mike"},{two,2}]).
hello Mike, 1, 2, 3
ok



ベンチマーク:

1> timer:tc(fout,benchmark_format_overhead,["hello ~s{name}, ~p{one}, ~p{two}, ~p{three}~n",[{one,1},{name,"Mike"},{three,3},{two,2}],100000]).
{421000,true}
= 4.21us per call

このオーバーヘッドの多くはループによるものだと思いますが、1つのループで関数を呼び出すと、1us未満の応答が得られます。

1> timer:tc(fout,benchmark_format_overhead,["hello ~s{name}, ~p{one}, ~p{two}, ~p{three}~n",[{one,1},{name,"Mike"},{three,3},{two,2}],1]).
{1,true}

erlangでベンチマークを行うためのより良い方法があれば、私に知らせてください。



コード:( ダグの提案に従って改訂されました)

-module(fout).

-export([format/2,benchmark_format_overhead/3]).

benchmark_format_overhead(_,_,0)->
    true;
benchmark_format_overhead(OString,OList,Loops) ->
    {FString,FNames}=parse_string(OString,ONames),
    benchmark_format_overhead(OString,OList,Loops-1).

format(OString,ONames) ->
    {FString,FNames}=parse_string(OString,ONames),
    io:format(FString,FNames).

parse_string(FormatString,Names) ->
    {F,N}=parse_format(FormatString),
    {F,substitute_names(N,Names)}.

parse_format(FS) ->
    parse_format(FS,"",[],"").

parse_format("",FormatString,ParamList,"")->
    {lists:reverse(FormatString),lists:reverse(ParamList)};
parse_format([${|FS],FormatString,ParamList,"")->
    parse_name(FS,FormatString,ParamList,"");
parse_format([$}|_FS],FormatString,_,_) ->
    throw({'unmatched } found',lists:reverse(FormatString)});
parse_format([C|FS],FormatString,ParamList,"") ->
    parse_format(FS,[C|FormatString],ParamList,"").

parse_name([$}|FS],FormatString,ParamList,ParamName) ->
    parse_format(FS,FormatString,[list_to_atom(lists:reverse(ParamName))|ParamList],"");
parse_name([${|_FS],FormatString,_,_) ->
    throw({'additional { found',lists:reverse(FormatString)});
parse_name([C|FS],FormatString,ParamList,ParamName) ->
    parse_name(FS,FormatString,ParamList,[C|ParamName]).

substitute_names(Positioned,Values) ->
    lists:map(fun(CN)->
                        case lists:keysearch(CN,1,Values) of
                            false ->
                                throw({'named parameter not found',CN,Values});
                            {_,{_,V}} ->
                                V
                        end end,
              Positioned).

これは学習演習だったので、erlangの経験が豊富な人が、コードを改善するためのヒントを教えてくれることを期待していました。

乾杯、マイク

4

3 に答える 3

2

アルゴリズムや適切なライブラリ関数の使用についてのコメントはありません...

パターン マッチングと再帰がもっと使われることを期待していたでしょう。たとえば、parse_character (折りたたまれていない) は次のようなものに置き換えられます。

parse_in_format ([], FmtStr, ParmStrs, ParmName) -> {FmtStr, ParmStrs};
parse_in_format ([${ | Vr], FmtStr, ParmStrs, ParmName) -> parse_in_name (Vr, FmtStr, ParmStrs, ParmName);
parse_in_format ([$} | Vr], FmtStr, ParmStrs, ParmName) -> throw() % etc.
parse_in_format ([V | Vr], FmtStr, ParmStrs, ParmName) -> parse_in_format (Vr, [V | FmtStr], ParmStrs, ParmName).

parse_in_name ([], FmtStr, ParmStrs, ParmName) -> throw() % etc.
parse_in_name ([$} | Vr], FmtStr, ParmStrs, ParmName) -> parse_in_format (Vr, FmtStr, [list_to_atom(lists:reverse(ParmName))|ParmStrs], "");
parse_in_name ([${ | Vr], FmtStr, ParmStrs, ParmName) -> throw() % etc.
parse_in_name ([V | Vr], FmtStr, ParmStrs, ParmName) -> parse_in_name (Vr, FmtStr, ParmStrs, [V | ParmName]).

でキックオフ

parse_in_format (FormatStr,  [], [], "");
于 2009-01-04T05:37:28.187 に答える
2

doug の提案に加えて、atom_to_list/1ここでの使用は避けたいと思います - 代替名のコードはそれらを必要とせず、実行時にアトムを生成することはほとんどの場合悪い考えです。文字列は完全にうまく機能します。

parse_name([$}|FS],FormatString,ParamList,ParamName) ->
    parse_format(FS,FormatString,[lists:reverse(ParamName)|ParamList],"");
parse_name([${|_FS],FormatString,_,_) ->
    throw({'additional { found',lists:reverse(FormatString)});
parse_name([C|FS],FormatString,ParamList,ParamName) ->
    parse_name(FS,FormatString,ParamList,[C|ParamName]).

代わりに proplists:get_value も使用します -ここで行っているようlists:keysearch/3に 2 つの要素タプルのリストがある場合、コードを使用するのが道です - 欠損値をチェックするために case ステートメントが必要なため、まだ少し面倒です。より良いエラーでクラッシュする可能性があります。{Name, Value}proplists

substitute_names(Positioned,Values) ->
    [ case proplists:get_value(Name, Values) of
          undefined -> erlang:exit({missing_parameter, Name});
          V -> V
      end
      || Name <- Positioned ].

これはライブラリであるためio_lib、 ではなくの代わりになるはずioです。ioこの方法では、提供されるすべての代替手段(オプションのIoDevice引数など)を提供する必要はありません。

format(OString,ONames) ->
    {FString,FNames}=parse_string(OString,ONames),
    io_lib:format(FString,FNames).

全体として、堅実なコードです。BSD などでライセンスを取得する意思がある場合は、私の Web フレームワーク コードEjangoに追加したいと思います。

于 2009-01-06T22:57:30.253 に答える
1

ループのオーバーヘッドがコードに大きな影響を与えるかどうかわからない場合は、それを測定する必要があります。それは簡単です。

-define(COLOOPS, 1000000).

-export([call_overhead/1,measure_call_overhead/0, measure_call_overhead/1]).

% returns overhead in us 
measure_call_overhead() -> measure_call_overhead(?COLOOPS).
measure_call_overhead(N) -> element(1, timer:tc(?MODULE, call_overhead, [N]))/N.

call_overhead(0)->ok;
call_overhead(N)->
    ok=nop(),
    call_overhead(N-1).

nop()->ok.

私のラップトップでは約50nsです。これは現在のコードにそれほど影響を与えるべきではないと思います。

測定する別の方法は、ms で時間を返す統計 (wall_clock) または統計 (ランタイム) を直接使用することです。利点は、測定された関数をエクスポートする必要がないことです。あくまでもコスパの向上です。

于 2009-01-04T08:17:03.603 に答える