[Erlang 0064] Erlang Array


 
從開始學習編程的時候Array就是基礎數據結構,也是被使用最頻繁的,但是在Erlang中一等公民是List和tuple,在項目中到處都是List的各種處理,但是Array卻少見蹤跡.好奇心驅使,最近了翻看了一下Array的代碼實現.
 

array基礎

  [1] array可動態擴展大小;可固定大小,可按需自動增長.
  [2] 如果沒有明確賦值會使用默認值undefined,要區分是否賦值過可以使用其它的值
  [3] 索引計數從0開始,這一設計決策是出於性能考慮
  [4] array從不自動收縮,如果索引i的位置被賦值了,中間的[0,i]都會處於可訪問的狀態(注意resize/2可能和你猜測的效果不同)
  [5] 數組不可直接比較大小
Eshell V5.9  (abort with ^G)
1>  A1 = array:set(17, true, array:new()).
{array,18,100,undefined,
       {10,
        {undefined,undefined,undefined,undefined,undefined,
                   undefined,undefined,true,undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
2>   array:set(17, undefined, array:new()). %%即使賦值的時候使用的是默認值,對應數據組依然會展開
{array,18,100,undefined,
       {10,
        {undefined,undefined,undefined,undefined,undefined,
                   undefined,undefined,undefined,undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
3>  array:set(1214, true, A1).
{array,1215,10000,undefined,
       {{{10,
          {undefined,undefined,undefined,undefined,undefined,
                     undefined,undefined,true,undefined,undefined},
          10,10,10,10,10,10,10,10,10},
         100,100,100,100,100,100,100,100,100,100},
        {100,100,
         {10,
          {undefined,undefined,undefined,undefined,true,undefined,
                     undefined,undefined,undefined,undefined},
          10,10,10,10,10,10,10,10,10},
         100,100,100,100,100,100,100,100},
        1000,1000,1000,1000,1000,1000,1000,1000,1000}}

%% array:reset

2> array:set(12,test,array:new()).
{array,13,100,undefined,
       {10,
        {undefined,undefined,test,undefined,undefined,undefined,
                   undefined,undefined,undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
4> array:reset(12,v(2)).
{array,13,100,undefined,
       {10,
        {undefined,undefined,undefined,undefined,undefined,
                   undefined,undefined,undefined,undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
5> 

看一下擴展的例子:
12> array:new({default,null}).
{array,0,10,null,10}
13>  array:get(24,  v(12)).  
null
15>  array:default( v(12)).   
null
16>  array:set(124, true, A1).
{array,125,1000,undefined,
       {{10,
         {undefined,undefined,undefined,undefined,undefined,
                    undefined,undefined,true,undefined,undefined},
         10,10,10,10,10,10,10,10,10},
        {10,10,
         {undefined,undefined,undefined,undefined,true,undefined,
                    undefined,undefined,undefined,undefined},
         10,10,10,10,10,10,10,10},
        100,100,100,100,100,100,100,100,100}}
17>  array:set(1214, true, A1).
{array,1215,10000,undefined,
       {{{10,
          {undefined,undefined,undefined,undefined,undefined,
                     undefined,undefined,true,undefined,undefined},
          10,10,10,10,10,10,10,10,10},
         100,100,100,100,100,100,100,100,100,100},
        {100,100,
         {10,
          {undefined,undefined,undefined,undefined,true,undefined,
                     undefined,undefined,undefined,undefined},
          10,10,10,10,10,10,10,10,10},
         100,100,100,100,100,100,100,100},
        1000,1000,1000,1000,1000,1000,1000,1000,1000}}
18>

 

還是tuple

   array數據結構的實現還是tuple,array被定義一個record,其中包含了一個tuple trees.
-record(array, {size :: non_neg_integer(),     %% number of defined entries
          max  :: non_neg_integer(),     %% maximum number of entries
                              %% in current tree
          default,     %% the default value (usually 'undefined')
          elements     %% the tuple tree
            }).
 
由於record本質上也是tuple,所以array.erl到處可見tuple的各種操作.先復習一下兩個使用頻率最高的函數erlang:make_tuple/2,3和setelement/3
Eshell V5.9  (abort with ^G)
1> erlang:make_tuple(5,a).
{a,a,a,a,a}
2>  erlang:make_tuple(4, []).
{[],[],[],[]}
3>  erlang:make_tuple(9, [],[{1,a},{3,q}]).
{a,[],q,[],[],[],[],[],[]}
4>

Eshell V5.9  (abort with ^G)
1> T={a,b,c,d}.
{a,b,c,d}
2> setelement(1,T,hello).
{hello,b,c,d}
3> setelement(2,T,kk).
{a,kk,c,d}
在array中下面這些操作其實都是在處理tuple,一起復習一下:
-define(NEW_NODE(S),  % beware of argument duplication!
     setelement((?NODESIZE+1),erlang:make_tuple((?NODESIZE+1),(S)),(S))).
    
-define(NEW_LEAF(D), erlang:make_tuple(?LEAFSIZE,(D))).


is_array(#array{size = Size, max = Max})
  when is_integer(Size), is_integer(Max) ->
    true;
is_array(_) ->
    false.

size(#array{size = N}) -> N;
size(_) -> erlang:error(badarg).


default(#array{default = D}) -> D;
default(_) -> erlang:error(badarg).

is_fix(#array{max = 0}) -> true;
is_fix(#array{}) -> false.

fix(#array{}=A) ->
    A#array{max = 0}.

relax(#array{size = N}=A) ->
    A#array{max = find_max(N-1, ?LEAFSIZE)}.   %%LEAFSIZE = 10
 

find_max(I, M) when I >= M ->
    find_max(I, ?extend(M));
find_max(_I, M) ->
    M.
 
    elements的構成是tuple tree,每LEAFSIZE(默認基數是10)個元素一組,如果對應數據位上的元素有值,那么該組的數據會展開,如果整組數據都沒有賦值,那么該組數據將以LEAFSIZE占位即10.即使賦值的時候使用的是默認值,對應數據組依然會展開;內部節點的最后一個元素緩存了每一個子樹可能存儲的子元素個數.
    從一個set方法就可以了解array中element的數據結構在運行時是如何使用的.這里會有兩個概念,第一個是"節點的展開",在節點沒有展開的時候是一個數字占位,當對節點內數據位進行賦值就需要展開節點即變成一個tuple.另外一個概念就是array的擴容,在array沒有固定大小的情況下,如果賦值的數據位置超過了當前array的最大值就會進行擴容.
set(I, Value, #array{size = N, max = M, default = D, elements = E}=A)
  when is_integer(I), I >= 0 ->
    if I < N ->  %%%小於當前Size 
               A#array{elements = set_1(I, E, Value, D)};
       I < M ->  %%%超過當前的Size但是小於MAX 點
               %% (note that this cannot happen if M == 0, since N >= 0)
               A#array{size = I+1,elements = set_1(I, E, Value, D)};
       M > 0 ->  %%%沒有Fix大小 會進行擴容
             {E1, M1} = grow(I, E, M),
             A#array{size = I+1, max = M1,elements = set_1(I, E1, Value, D)};
       true ->
              erlang:error(badarg)
    end;
set(_I, _V, _A) ->
    erlang:error(badarg).

%% See get_1/3 for details about switching and the NODEPATTERN macro.

set_1(I, E=?NODEPATTERN(S), X, D) ->
    I1 = I div S + 1,
    setelement(I1, E, set_1(I rem S, element(I1, E), X, D));
set_1(I, E, X, D) when is_integer(E) ->
    expand(I, E, X, D);
set_1(I, E, X, _D) ->
    setelement(I+1, E, X).


%% Enlarging the array upwards to accommodate an index `I'

grow(I, E, _M) when is_integer(E) ->
    M1 = find_max(I, E),
    {M1, M1};
grow(I, E, M) ->
    grow_1(I, E, M).

grow_1(I, E, M) when I >= M ->
    grow(I, setelement(1, ?NEW_NODE(M), E), ?extend(M));
grow_1(_I, E, M) ->
    {E, M}.


%% Insert an element in an unexpanded node, expanding it as necessary.

expand(I, S, X, D) when S > ?LEAFSIZE ->
    S1 = ?reduce(S),
    setelement(I div S1 + 1, ?NEW_NODE(S1),
            expand(I rem S1, S1, X, D));
expand(I, _S, X, D) ->
    setelement(I+1, ?NEW_LEAF(D), X).
   對於size在array中是一個比較弱化的概念,size變化規則:元素位置參數i是非負整數,如果array固定了size(執行了fix函數)那么如果i大於array的size就會報錯;對於沒有固定大小的array,如果i>size(Array)-1,Array的Size會擴展到i+1;
Eshell V5.9  (abort with ^G)
1>  A1 = array:set(17, true, array:new()).
{array,18,100,undefined,
       {10,
        {undefined,undefined,undefined,undefined,undefined,
                   undefined,undefined,true,undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
   對於數據量較大的情況,提高速度的關鍵是減少嘗試的次數.盡量最有可能的路徑盡量的短. 特別是要注意的是對於較大的樹,訪問到一個葉子節點的概率比訪問到一個內部節點的概率要小很多.如果要微調 set_1 get_1一定要反復測試,參數的順序都會影響效率.get方法使用了稀疏遍歷的方式盡可能快的定位到要訪問的元素.
  
%% -define(NODEPATTERN(S), {_,_,_,_,_,_,_,_,_,_,S}). % NODESIZE+1 elements!

get(I, #array{size = N, max = M, elements = E, default = D})
  when is_integer(I), I >= 0 ->
    if I < N ->
         get_1(I, E, D);
       M > 0 ->
         D;
       true ->
         erlang:error(badarg)
    end;
get(_I, _A) ->
    erlang:error(badarg).

%% The use of NODEPATTERN(S) to select the right clause is just a hack,
%% but it is the only way to get the maximum speed out of this loop
%% (using the Beam compiler in OTP 11).

get_1(I, E=?NODEPATTERN(S), D) ->
    get_1(I rem S, element(I div S + 1, E), D);
get_1(_I, E, D) when is_integer(E) ->
    D;
get_1(I, E, _D) ->
    element(I+1, E).
  寫個例子看看 NODEPATTERN(S):
20> A = array:from_list([1,2,3,4,5,a,b,c,d,e,f,g]).
{array,12,100,undefined,
       {{1,2,3,4,5,a,b,c,d,e},
        {f,g,undefined,undefined,undefined,undefined,undefined,
           undefined,undefined,undefined},
        10,10,10,10,10,10,10,10,10}}

23> {_,_,_,_,E} =A.
{array,12,100,undefined,
       {{1,2,3,4,5,a,b,c,d,e},
        {f,g,undefined,undefined,undefined,undefined,undefined,
           undefined,undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
24>  {_,_,_,_,_,_,_,_,_,_,S} = E .
{{1,2,3,4,5,a,b,c,d,e},
{f,g,undefined,undefined,undefined,undefined,undefined,
    undefined,undefined,undefined},
10,10,10,10,10,10,10,10,10}
25> S.
10
  array有兩種遍歷方式,一種是逐一遍歷比如方法foldl foldr map,另外一種就是稀疏遍歷比如sparse_to_list sparse_to_orddict sparse_map sparse_size sparse_to_list sparse_to_orddict
1> A= array:set(17, true, array:new()).
{array,18,100,undefined,
       {10,
        {undefined,undefined,undefined,undefined,undefined,
                   undefined,undefined,true,undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
2> A2= array:set(30, ki, A).
{array,31,100,undefined,
       {10,
        {undefined,undefined,undefined,undefined,undefined,
                   undefined,undefined,true,undefined,undefined},
        10,
        {ki,undefined,undefined,undefined,undefined,undefined,
            undefined,undefined,undefined,undefined},
        10,10,10,10,10,10,10}}
3> array:sparse_to_list(A2).
[true,ki]
4>
 

數據結構轉換

  上面也提到了array可以在List和orddict之間進行轉換,稀疏方式轉換會忽略掉默認值.所以如果使用稀疏方式反復進行轉換,得到的array內部結構可能不同.看一下下面v(7) v(8)的值.
from_list(List::list()) -> array()
from_list(List::list(), Default::term()) -> array()
to_list(Array::array()) -> list()
sparse_to_list(Array::array()) -> list()  
Eshell V5.9  (abort with ^G)
1> 
1> array:from_list(lists:seq(1,12)).
{array,12,100,undefined,
       {{1,2,3,4,5,6,7,8,9,10},
        {11,12,undefined,undefined,undefined,undefined,undefined,
         undefined,undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
2>  array:to_list(v(1)).
[1,2,3,4,5,6,7,8,9,10,11,12]
3>  array:sparse_to_list(v(1)).
[1,2,3,4,5,6,7,8,9,10,11,12]
4> array:set(17,kk,v(1)).
{array,18,100,undefined,
       {{1,2,3,4,5,6,7,8,9,10},
        {11,12,undefined,undefined,undefined,undefined,undefined,kk,
         undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
5> array:to_list(v(4)).
[1,2,3,4,5,6,7,8,9,10,11,12,undefined,undefined,undefined,
undefined,undefined,kk]
6>  array:sparse_to_list(v(4)).
[1,2,3,4,5,6,7,8,9,10,11,12,kk]
7> array:from_list(v(6)).
{array,13,100,undefined,
       {{1,2,3,4,5,6,7,8,9,10},
        {11,12,kk,undefined,undefined,undefined,undefined,undefined,
         undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
8> array:from_list(v(5)).
{array,18,100,undefined,
       {{1,2,3,4,5,6,7,8,9,10},
        {11,12,undefined,undefined,undefined,undefined,undefined,kk,
         undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
 
 orddict和array之間的轉換類似,只不過由於orddict已經包含了數據索引的位置,所以反復轉換依然可以重建成為一致的數據結構.
 
 Eshell V5.9  (abort with ^G)
1> array:from_list(lists:seq(1,12)).
{array,12,100,undefined,
       {{1,2,3,4,5,6,7,8,9,10},
        {11,12,undefined,undefined,undefined,undefined,undefined,
         undefined,undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
2>  array:set(17,kk,v(1)).
{array,18,100,undefined,
       {{1,2,3,4,5,6,7,8,9,10},
        {11,12,undefined,undefined,undefined,undefined,undefined,kk,
         undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
3> array:to_orddict(v(1)).
[{0,1},{1,2},{2,3},{3,4},{4,5},{5,6},{6,7},{7,8},{8,9},{9,10},{10,11},{11,12}]
4> array:to_orddict(v(2)).
[{0,1},{1,2},{2,3},{3,4},{4,5},{5,6},{6,7},{7,8},{8,9},{9,10},{10,11},{11,12},{12,undefined},{13,undefined},{14,undefined},{15,undefined},{16,undefined},{17,kk}]
5> array:sparse_to_orddict(v(2)).
[{0,1},{1,2},{2,3},{3,4},{4,5},{5,6},{6,7},{7,8},{8,9},{9,10},{10,11},{11,12},{17,kk}]
6> array:from_orddict(v(4)).
{array,18,100,undefined,
       {{1,2,3,4,5,6,7,8,9,10},
        {11,12,undefined,undefined,undefined,undefined,undefined,kk,
         undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
7> array:from_orddict(v(5)).
{array,18,100,undefined,
       {{1,2,3,4,5,6,7,8,9,10},
        {11,12,undefined,undefined,undefined,undefined,undefined,kk,
         undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
8> array:from_orddict(v(3)).
{array,12,100,undefined,
       {{1,2,3,4,5,6,7,8,9,10},
        {11,12,undefined,undefined,undefined,undefined,undefined,
         undefined,undefined,undefined},
        10,10,10,10,10,10,10,10,10}}
9> 

 

Code snippet

 
代碼有兩處細節平時很少這樣寫:
Eshell V5.9  (abort with ^G)
1> F=fun(M,N)->{M,N} end.
#Fun<erl_eval.12.111823515>
2> F2=fun(K)->F(K,if K>0->K;true -> 100 end) end.
#Fun<erl_eval.6.111823515>
3> F2(10).
{10,10}
4> F2(-1).
{-1,100}
5>

5> F=fun(E={_,_,A})-> {A} end .  
#Fun<erl_eval.6.111823515>
7> F({1,2,a}).
{a}
8> F2=fun({_,_,A}=E)-> {A} end .
#Fun<erl_eval.6.111823515>
9> F2({1,2,a}).
{a}

 

Ya解決方案

2012-07-11 15:40 更新

https://github.com/baryluk/ral

% Random access list. % Random access lists (ral) is a functional (immutable) data % structure which are better (in speed) both from normal lists, % and from balanced binary search trees. % Time complexity of various basic operations % % Operation RAL List BST % head O(1) O(1) O(log n) % tail O(1) O(1) O(log n) % cons O(1) O(1) O(log n) % nth O(log n) O(n) O(log n) % nthtail O(log n) O(n) O(log n) % % in some situations nth or nthtail can be faster in RAL. O(log n) is worst case complexity. % for some values it is faster.

LYSE上一段很好的總結:

Erlang arrays, at the opposite of their imperative counterparts, are not able to have such things as constant-time insertion or lookup. Because they're usually slower than those in languages which support destructive assignment and that the style of programming done with Erlang doesn't necessary lend itself too well to arrays and matrices, they are rarely used in practice.

Generally, Erlang programmers who need to do matrix manipulations and other uses requiring arrays tend to use concepts called Ports to let other languages do the heavy lifting, or C-Nodes, Linked in drivers and NIFs (Experimental, R13B03+).

Arrays are also weird in the sense that they're one of the few data structures to be 0-indexed (at the opposite of tuples or lists), along with indexing in the regular expressions module. Be careful with them.

Link: http://learnyousomeerlang.com/a-short-visit-to-common-data-structures#arrays

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                                                        晚安!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM