2009-01-04 57 views
6

Archaelus在this post中建议编写一个新的格式例程来处理命名参数可能是一个很好的学习练习。因此,本着学习语言的精神,我编写了一个处理命名参数的格式化例程。打印命名参数



为例:

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 

虽然我怀疑第在这个开销的大部分是由于循环,作为一个调用函数与一个循环产生一个响应在< 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可以给我提示如何改进我的代码。

干杯, 迈克

+0

为了计时的目的,如果测量速度太快,您应该在循环中运行并取平均值作为时间 – 2009-01-04 04:52:25

+0

请在标题中更改您的问题,我不知道您问我是否看到了它在搜索结果中。 – Soviut 2009-07-22 17:25:13

回答

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, [], [], ""); 
+0

感谢Doug,我同意这种方法更好,我现在使用更少的库函数重写它。 Erlang是我的第一个函数式语言,所以我仍在向功能思维过渡。 感谢您的评论! – 2009-01-04 05:55:23

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。我认为这不应该太影响你当前的代码。

另一种如何测量的方法是直接使用统计数据(wall_clock)或统计数据(运行时间),以毫秒为单位返回时间。好处是您不需要导出测量功能。这只是化妆品的改进。

2

除了道格的建议,我会避免在这里使用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 - 当你有两个元素的元组{Name, Value}的列表,我们在这里,使用proplists代码的路要走 - 它仍然是我们有点乱需要case语句来检查缺少的值,以便我们可以更好地处理错误。

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或类似的方式进行许可,我很想将其添加到我的网络框架代码Ejango