Queue 是Erlang的隊列,它的內部實現充分考慮到了效率,值得學習.估計"如何用鏈表高效實現Queue"這個也會在面試題目中頻繁出現吧.queue模塊中除了len/1, join/2, split/2, filter/2 and member/2復雜度是O(n)之外所有的操作的復雜度都在O(1).怎么做到的呢?

巧妙的實現
queue使用兩個list來實現,這兩個List為{RearList,FrontList} 即{尾端,前端},queue的第一個元素出現在FrontList的Head;queue的最后一個元素出現在RearList的Head.下面創建,檢查,轉換的方法的執行效率是比較明顯的,凡是涉及到遍歷的,復雜度都在O(L).
%% O(1) new() -> {[],[]}. %{RearList,FrontList} %% O(1) is_queue({R,F}) when is_list(R), is_list(F) -> true; is_queue(_) -> false. %% O(1) is_empty({[],[]}) -> true; is_empty({In,Out}) when is_list(In), is_list(Out) -> false; is_empty(Q) -> erlang:error(badarg, [Q]). %% O(len(Q)) len({R,F}) when is_list(R), is_list(F) -> length(R)+length(F); len(Q) -> erlang:error(badarg, [Q]). %% O(len(Q)) to_list({In,Out}) when is_list(In), is_list(Out) -> Out++lists:reverse(In, []); to_list(Q) -> erlang:error(badarg, [Q]). %% O(length(L)) from_list(L) when is_list(L) -> f2r(L); from_list(L) -> erlang:error(badarg, [L]).
queue模塊所有的方法都會進行數據結構檢查,不符合Queue的特征就會拋出異常.由於內部數據結構使用的是List,improper list會引發內部崩潰.索引超出界限會拋出badarg的錯誤.為了減少queue操作的復雜性,隊列本身並沒維護長度信息,所以len/1要經過遍歷得到所以復雜度是O(n).如果對長度信息敏感,調用者很容易維護queue的長度信息.
list轉換到queue的過程使用的f2r方法的作用是:至少有三個元素的情況下,將兩個數據元素從尾端RearList移動到前段FrontList,r2f是其逆過程;對應的還有注意下面兩個方法是做了inline編譯優化的,這個之前已經提到過:[Erlang 0029] Erlang Inline編譯
%% Internal workers -compile({inline, [{r2f,1},{f2r,1}]}). %%至少有三個元素的情況下,將兩個數據元素從尾端RearList移動到前段FrontList %% Move all but two from R to F, if there are at least three r2f([]) -> {[],[]}; r2f([_]=R) -> {[],R}; r2f([X,Y]) -> {[X],[Y]}; r2f([X,Y|R]) -> {[X,Y],lists:reverse(R, [])}. %% Move all but two from F to R, if there are enough f2r([]) -> {[],[]}; f2r([_]=F) -> {F,[]}; f2r([X,Y]) -> {[Y],[X]}; f2r([X,Y|F]) -> {lists:reverse(F, []),[X,Y]}.
%%%%queue:f2r的測試代碼 1> q:f2r([a]). {[a],[]} 2> q:f2r([a,b]). {[b],[a]} 3> q:f2r([a,b,c]). {[c],[a,b]} 4> q:f2r([a,b,c,d]). {[d,c],[a,b]} 10> q:f2r([a,b,c,d,e,f,g,h,i,j]). {[j,i,h,g,f,e,d,c],[a,b]} 11> q:f2r([a,b,c,d,e,f,g,h,i,j,k]). {[k,j,i,h,g,f,e,d,c],[a,b]} 12> q:f2r(lists:seq(1,100)). {[100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82, 81,80,79,78,77,76,75,74,73|...], [1,2]} 13> q:r2f(lists:seq(1,100)). {[1,2], [100,99,98,97,96,95,94,93,92,91,90,89,88,87,86,85,84,83,82, 81,80,79,78,77,76,75,74|...]} 14> q:r2f([a,b]). {[a],[b]} 15> q:r2f([a,b,c]). {[a,b],[c]} 16> q:r2f([a,b,c,d]). {[a,b],[d,c]} 17> %% Return true or false depending on if element is in queue %% %% O(length(Q)) worst case -spec member(Item :: term(), Q :: queue()) -> boolean(). member(X, {R,F}) when is_list(R), is_list(F) -> lists:member(X, R) orelse lists:member(X, F); member(X, Q) -> erlang:error(badarg, [X,Q]).
2014-4-3 17:46:36 補充 現在f2r r2f已經換了實現,感謝同學提醒,好細心的說
-compile({inline, [{r2f,1},{f2r,1}]}). %% Move half of elements from R to F, if there are at least three r2f([]) -> {[],[]}; r2f([_]=R) -> {[],R}; r2f([X,Y]) -> {[X],[Y]}; r2f(List) -> {FF,RR} = lists:split(length(List) div 2 + 1, List), {FF,lists:reverse(RR, [])}. %% Move half of elements from F to R, if there are enough f2r([]) -> {[],[]}; f2r([_]=F) -> {F,[]}; f2r([X,Y]) -> {[Y],[X]}; f2r(List) -> {FF,RR} = lists:split(length(List) div 2 + 1, List), {lists:reverse(RR, []),FF}.
入隊列操作實際上就是不斷的在RearList頭部追加元素,所以這里的復雜度也是O(1).
%% O(1) in(X, {[_]=In,[]}) -> {[X], In}; in(X, {In,Out}) when is_list(In), is_list(Out) -> {[X|In],Out}; in(X, Q) -> erlang:error(badarg, [X,Q]).
測試代碼:
Eshell V5.9 (abort with ^G) 1> queue:new(). {[],[]} 2> queue:in(a,v(1)). {[a],[]} 3> queue:in(b,v(2)). {[b],[a]} 4> queue:in(c,v(3)). {[c,b],[a]} 5> queue:in(d,v(4)). {[d,c,b],[a]} 6> queue:in(e,v(5)). {[e,d,c,b],[a]} 7> queue:in(f,v(6)). {[f,e,d,c,b],[a]} 8> queue:in(g,v(7)). {[g,f,e,d,c,b],[a]} 9>
出隊列通常復雜度也是咋O(1),最差的情況是O(len(Q));對於RearList和FrontList都有數據的情況下,取出一個數據元素僅僅是從FrontList中取出頭元素,所以時間復雜度也是1.如果恰好取出了FrontList的最后一個元素,就要做前后端數據元素的轉移.
%% O(1) amortized, O(len(Q)) worst case out({[],[]}=Q) -> {empty,Q}; out({[V],[]}) -> {{value,V},{[],[]}}; out({[Y|In],[]}) -> [V|Out] = lists:reverse(In, []), {{value,V},{[Y],Out}}; out({In,[V]}) when is_list(In) -> {{value,V},r2f(In)}; out({In,[V|Out]}) when is_list(In) -> {{value,V},{In,Out}}; out(Q) -> erlang:error(badarg, [Q]).
10> queue:out(v(8)). {{value,a},{[g,f],[b,c,d,e]}} 11> {_,R}=queue:out(v(8)). {{value,a},{[g,f],[b,c,d,e]}} 12> {_,R2}=queue:out(R). {{value,b},{[g,f],[c,d,e]}} 13> {_,R3}=queue:out(R2). {{value,c},{[g,f],[d,e]}} 14> {_,R4}=queue:out(R3). {{value,d},{[g,f],[e]}} 15>
類似的情況還有get方法,大多數情況下復雜度是O(1),最差的情況下需要去RearList的最后一個元素,時間復雜度是O(len(Q)).
%% O(1) since the queue is supposed to be well formed get({[],[]}=Q) -> erlang:error(empty, [Q]); get({R,F}) when is_list(R), is_list(F) -> get(R, F); get(Q) -> erlang:error(badarg, [Q]). get(R, [H|_]) when is_list(R) -> H; get([H], []) -> H; get([_|R], []) -> % malformed queue -> O(len(Q)) lists:last(R).
逆轉整個隊列這樣的操作時間復雜度也是O(1),因為只需要把前后段兩個List互換就可以了;
%% O(1) reverse({R,F}) when is_list(R), is_list(F) -> {F,R}; reverse(Q) -> erlang:error(badarg, [Q]).
三種API
queue是雙端數據結構.模塊的接口比較豐富,分為: "Original API", the "Extended API" and the "Okasaki API".Original API 和 Extended API 接口都可以用隊列進出模型去理解,其中進行了reverse操作都會添加"_r"的后綴.
官方文檔中的說法:
The "Original API" item removal functions return compound terms with both the removed item and the resulting queue. The "Extended API" contain alternative functions that build less garbage as well as functions for just inspecting the queue ends. Also the "Okasaki API" functions build less garbage.
The "Okasaki API" is inspired by "Purely Functional Data structures" by Chris Okasaki. It regards queues as lists. The API is by many regarded as strange and avoidable. For example many reverse operations have lexically reversed names, some with more readable but perhaps less understandable aliases.
Learn you some Erlang的解釋:
-
- Original API
-
The original API contains the functions at the base of the queue concept, including:
new/0
, for creating empty queues,in/2
, for inserting new elements,out/1
, for removing elements, and then functions to convert to lists, reverse the queue, look if a particular value is part of it, etc. - Extended API
-
The extended API mainly adds some introspection power and flexibility: it lets you do things such as looking at the front of the queue without removing the first element (see
get/1
orpeek/1
), removing elements without caring about them (drop/1
), etc. These functions are not essential to the concept of queues, but they're still useful in general. - Okasaki API
-
The Okasaki API is a bit weird. It's derived from Chris Okasaki's Purely Functional Data Structures. The API provides operations similar to what was available in the two previous APIs, but some of the function names are written backwards and the whole thing is relatively peculiar. Unless you do know you want this API, I wouldn't bother with it.
2014年4月3日13:46:11補兩段代碼,供參考
3> Q=queue:from_list([1,2,3,4,5,6,7,8,9,a,b,c,d]). {[d,c,b,a,9,8],[1,2,3,4,5,6,7]} 4> queue:in(m,Q). {[m,d,c,b,a,9,8],[1,2,3,4,5,6,7]} 6> queue:in(n,v(4)). {[n,m,d,c,b,a,9,8],[1,2,3,4,5,6,7]} 7> queue:in_r(m,Q). {[d,c,b,a,9,8],[m,1,2,3,4,5,6,7]} 8> queue:out(v(7)). {{value,m},{[d,c,b,a,9,8],[1,2,3,4,5,6,7]}} 9> queue:out_r(v(7)). {{value,d},{[c,b,a,9,8],[m,1,2,3,4,5,6,7]}} 10> 10> queue:to_list(v(7)). [m,1,2,3,4,5,6,7,8,9,a,b,c,d] 1> Q=queue:from_list([1,2,3,4,5,6,7,8,9,a,b,c,d]). {[d,c,b,a,9,8],[1,2,3,4,5,6,7]} 2> queue:to_list(queue:in(m,Q)). [1,2,3,4,5,6,7,8,9,a,b,c,d,m] 3> queue:to_list(queue:in_r(m,Q)). [m,1,2,3,4,5,6,7,8,9,a,b,c,d]
晚安!
