2009-01-04 79 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