Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
...
  • 4 commits
  • 4 files changed
  • 0 commit comments
  • 1 contributor
Showing with 210 additions and 1,923 deletions.
  1. +99 −14 e3d/e3d_mat.erl
  2. +101 −8 e3d/e3d_q.erl
  3. +10 −2 e3d/e3d_vec.erl
  4. +0 −1,899 src/array.erl
View
113 e3d/e3d_mat.erl
@@ -16,14 +16,17 @@
-export([identity/0,is_identity/1,determinant/1,print/1,
compress/1,expand/1,
translate/1,translate/3,scale/1,scale/3,
- rotate/2,rotate_to_z/1,rotate_s_to_t/2,
+ rotate/2,rotate_from_euler_rad/1, rotate_from_euler_rad/3,
+ rotate_to_z/1,rotate_s_to_t/2,
project_to_plane/1,
transpose/1,invert/1,
- mul/2,mul_point/2,mul_vector/2,eigenv3/1]).
+ add/2,mul/2,mul_point/2,mul_vector/2,eigenv3/1]).
-compile(inline).
-include("e3d.hrl").
-spec identity() -> e3d_compact_matrix().
+
+-define(EPSILON, 1.0e-06).
identity() ->
Zero = 0.0,
@@ -45,7 +48,11 @@ is_identity({_,_,_,_,_,_,_,_,_,_,_,_}) -> false.
-spec compress(e3d_matrix()) -> e3d_compact_matrix().
compress(identity=I) -> I;
-compress({A,B,C,0.0,D,E,F,0.0,G,H,I,0.0,Tx,Ty,Tz,1.0}) ->
+compress({A,B,C,Z1,D,E,F,Z2,G,H,I,Z3,Tx,Ty,Tz,One}) ->
+ case eps(Z1) andalso eps(Z2) andalso eps(Z3) andalso eps(One-1.0) of
+ false -> exit(not_compressable);
+ true -> ok
+ end,
{A,B,C,D,E,F,G,H,I,Tx,Ty,Tz};
compress(Mat)
when tuple_size(Mat) =:= 12 ->
@@ -112,6 +119,30 @@ rotate(A0, {X,Y,Z}) when is_float(X), is_float(Y), is_float(Z) ->
U3+NegS*U3+C3, U6+NegS*U6+C6, U9+S*(1.0-U9),
0.0,0.0,0.0}.
+%% rotate_from_euler_rad is a shortcut for
+%% Rad2deg = 180/math:pi(),
+%% Mx = e3d_mat:rotate(Rx * Rad2deg, {1.0, 0.0, 0.0}),
+%% My = e3d_mat:rotate(Ry * Rad2deg, {0.0, 1.0, 0.0}),
+%% Mz = e3d_mat:rotate(Rz * Rad2deg, {0.0, 0.0, 1.0}),
+%% Rot = e3d_mat:mul(Mz, e3d_mat:mul(My,Mx)),
+-spec rotate_from_euler_rad(Vector::e3d_vector()) -> e3d_compact_matrix().
+rotate_from_euler_rad({X,Y,Z}) ->
+ rotate_from_euler_rad(X,Y,Z).
+
+-spec rotate_from_euler_rad(X::number(), Y::number(), Z::number()) ->
+ e3d_compact_matrix().
+rotate_from_euler_rad(Rx,Ry,Rz) ->
+ Cz = math:cos(Rz), Sz = math:sin(Rz),
+ Cy = math:cos(Ry), Sy = math:sin(Ry),
+ Cx = math:cos(Rx), Sx = math:sin(Rx),
+ Sxsy=Sx*Sy, Cxsy=Cx*Sy,
+ TZ = 0.0,
+ {Cy*Cz, Cy*Sz, -Sy,
+ Sxsy*Cz-Cx*Sz, Sxsy*Sz+Cx*Cz, Sx*Cy,
+ Cxsy*Cz+Sx*Sz, Cxsy*Sz-Sx*Cz, Cx*Cy,
+ TZ, TZ, TZ}.
+
+
%% Project to plane perpendicular to vector Vec.
-spec project_to_plane(Vector::e3d_vector()) -> e3d_compact_matrix().
@@ -173,12 +204,47 @@ transpose({M1,M2,M3,M4,M5,M6,M7,M8,M9,0.0=Z,0.0,0.0}) ->
M3,M6,M9,
Z,Z,Z}.
--spec mul(M::e3d_matrix(), N::e3d_matrix() | e3d_vector() |
- {float(),float(),float(),float()}) ->
- e3d_matrix() | {float(),float(),float(),float()}.
-
+-spec add(M::e3d_matrix(), N::e3d_matrix()) -> e3d_matrix().
+add({B_a,B_b,B_c,B_d,B_e,B_f,B_g,B_h,B_i,B_tx,B_ty,B_tz},
+ {A_a,A_b,A_c,A_d,A_e,A_f,A_g,A_h,A_i,A_tx,A_ty,A_tz})
+ when is_float(A_a), is_float(A_b), is_float(A_c), is_float(A_d), is_float(A_e),
+ is_float(A_f), is_float(A_g), is_float(A_h), is_float(A_i), is_float(A_tx),
+ is_float(A_ty), is_float(A_tz),
+ is_float(B_a), is_float(B_b), is_float(B_c), is_float(B_d), is_float(B_e),
+ is_float(B_f), is_float(B_g), is_float(B_h), is_float(B_i), is_float(B_tx),
+ is_float(B_ty), is_float(B_tz) ->
+ {A_a+B_a, A_b+B_b, A_c+B_c,
+ A_d+B_d, A_e+B_e, A_f+B_f,
+ A_g+B_g, A_h+B_h, A_i+B_i,
+ A_tx+B_tx, A_ty+B_ty, A_tz+B_tz};
+add({B_a,B_b,B_c,B_w0,B_d,B_e,B_f,B_w1,B_g,B_h,B_i,B_w2,B_tx,B_ty,B_tz,B_w3},
+ {A_a,A_b,A_c,A_w0,A_d,A_e,A_f,A_w1,A_g,A_h,A_i,A_w2,A_tx,A_ty,A_tz,A_w3})
+ when is_float(A_a), is_float(A_b), is_float(A_c), is_float(A_d), is_float(A_e),
+ is_float(A_f), is_float(A_g), is_float(A_h), is_float(A_i), is_float(A_tx),
+ is_float(A_ty), is_float(A_tz),
+ is_float(A_w0),is_float(A_w1),is_float(A_w2),is_float(A_w3),
+ is_float(B_a), is_float(B_b), is_float(B_c), is_float(B_d), is_float(B_e),
+ is_float(B_f), is_float(B_g), is_float(B_h), is_float(B_i), is_float(B_tx),
+ is_float(B_ty), is_float(B_tz),
+ is_float(B_w0),is_float(B_w1),is_float(B_w2),is_float(B_w3) ->
+ {A_a+B_a, A_b+B_b, A_c+B_c, A_w0+B_w0,
+ A_d+B_d, A_e+B_e, A_f+B_f, A_w1+B_w1,
+ A_g+B_g, A_h+B_h, A_i+B_i, A_w2+B_w2,
+ A_tx+B_tx, A_ty+B_ty, A_tz+B_tz, A_w3+B_w3};
+
+add(M1,M2) when tuple_size(M1) =:= 12; tuple_size(M2) =:= 12 ->
+ add(e3d_mat:expand(M1), e3d_mat:expand(M2)).
+
+
+-spec mul(M::e3d_matrix(), N::e3d_matrix()) ->
+ e3d_matrix();
+ (M::e3d_matrix(), number()) ->
+ e3d_matrix();
+ (M::e3d_matrix(), {float(),float(),float(),float()}) ->
+ {float(),float(),float(),float()}.
mul(M, identity) -> M;
-mul(identity, M) -> M;
+mul(identity, M) when not is_number(M) -> M;
+mul(identity,M2) -> mul(expand(identity), M2);
mul({1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0,B_tx,B_ty,B_tz},
{A_a,A_b,A_c,A_d,A_e,A_f,A_g,A_h,A_i,A_tx,A_ty,A_tz})
when is_float(A_tx), is_float(A_ty), is_float(A_tz),
@@ -236,7 +302,7 @@ mul({B_a,B_b,B_c,B_d,B_e,B_f,B_g,B_h,B_i,B_j,B_k,B_l,B_tx,B_ty,B_tz,B_w},
A_tx*B_d + A_ty*B_h + A_tz*B_l + A_w*B_w};
mul({A,B,C,Q0,D,E,F,Q1,G,H,I,Q2,Tx,Ty,Tz,Q3}, {X,Y,Z,W})
when is_float(A), is_float(B), is_float(C), is_float(D), is_float(E),
- is_float(F), is_float(G), is_float(H), is_float(I),
+ is_float(F), is_float(G), is_float(H), is_float(I),
is_float(Tx), is_float(Ty), is_float(Tz),
is_float(Q0), is_float(Q1), is_float(Q2), is_float(Q3),
is_float(X), is_float(Y), is_float(Z) ->
@@ -244,7 +310,25 @@ mul({A,B,C,Q0,D,E,F,Q1,G,H,I,Q2,Tx,Ty,Tz,Q3}, {X,Y,Z,W})
X*B + Y*E + Z*H + W*Ty,
X*C + Y*F + Z*I + W*Tz,
X*Q0 + Y*Q1 + Z*Q2 + W*Q3};
-mul(M1,M2)
+mul({A,B,C,Q0,D,E,F,Q1,G,H,I,Q2,Tx,Ty,Tz,Q3}, W)
+ when is_float(A), is_float(B), is_float(C), is_float(D), is_float(E),
+ is_float(F), is_float(G), is_float(H), is_float(I),
+ is_float(Tx), is_float(Ty), is_float(Tz),
+ is_float(Q0), is_float(Q1), is_float(Q2), is_float(Q3),
+ is_number(W) ->
+ {A*W, B*W, C*W, Q0*W,
+ D*W, E*W, F*W, Q1*W,
+ G*W, H*W, I*W, Q2*W,
+ Tx*W,Ty*W,Tz*W, Q3*W};
+mul({A,B,C,D,E,F,G,H,I,Tx,Ty,Tz}, W)
+ when is_float(A), is_float(B), is_float(C), is_float(D), is_float(E),
+ is_float(F), is_float(G), is_float(H), is_float(I),
+ is_float(Tx), is_float(Ty), is_float(Tz), is_number(W) ->
+ {A*W, B*W, C*W,
+ D*W, E*W, F*W,
+ G*W, H*W, I*W,
+ Tx*W,Ty*W,Tz*W};
+mul(M1,M2)
when tuple_size(M1) =:= 12; tuple_size(M2) =:= 12 ->
mul(expand(M1), expand(M2)).
@@ -343,7 +427,7 @@ invert({M0, M1, M2, M3,
B4 = M9*M15 - M11*M13, B5 = M10*M15 - M11*M14,
Det = A0*B5 - A1*B4 + A2*B3 + A3*B2 - A4*B1 + A5*B0,
- case abs(Det) > 0.00005 of
+ case abs(Det) > ?EPSILON of
true ->
InvDet = 1.0/Det,
{(+ M5*B5 - M6*B4 + M7*B3) * InvDet,
@@ -397,8 +481,6 @@ print_1(_Mat = {A,B,C,D,E,F,G,H,I,J,K,L,TX,TY,TZ,W}) ->
%% Returns ordered by least EigenValue first
%% {Evals={V1,V2,V3},Evects={X1,Y1,Z1,X2,Y2,Z2,X3,Y3,Z3}}
--define(EIG_EPS, 1.0e-06).
-
eigenv3(Mat0={A,B,C,D,E,F,G,H,I})
when is_float(A), is_float(B), is_float(C),
is_float(D), is_float(E), is_float(F),
@@ -486,7 +568,7 @@ eig_triDiag3({A0,B0,C0,_,D0,E0,_,_,F0})
when is_float(A0), is_float(B0), is_float(C0),
is_float(D0), is_float(E0), is_float(F0) ->
Di0 = A0,
- if abs(C0) >= ?EIG_EPS ->
+ if abs(C0) >= ?EPSILON ->
Ell = math:sqrt(B0*B0+C0*C0),
B = B0/Ell,
C = C0/Ell,
@@ -506,6 +588,9 @@ eig_triDiag3({A0,B0,C0,_,D0,E0,_,_,F0})
{Mat,{Di0,D0,F0},{B0,E0,0.0}}
end.
+eps(E) ->
+ abs(E) < ?EPSILON.
+
-define(S(I),element(I+1,Subd0)).
-define(D(I),element(I+1,Diag0)).
-define(Set(I,Val,Tup),setelement(I+1,Tup,Val)).
View
109 e3d/e3d_q.erl
@@ -19,10 +19,12 @@
-module(e3d_q).
-export([identity/0,inverse/1,norm/1,mul/1,mul/2,
- add/2, scale/2,
+ add/2, scale/2, slerp/3,
magnitude/1, conjugate/1,
to_rotation_matrix/1, from_rotation_matrix/1,
- from_angle_axis/2, to_angle_axis/1,
+ to_angle_axis/1, from_angle_axis/2, from_angle_axis_rad/2,
+ from_euler_rad/1, from_euler_rad/3, to_euler_rad/1,
+ from_compact/1, to_compact/1,
rotate_s_to_t/2,
vec_rotate/2]).
@@ -34,8 +36,12 @@ identity() ->
magnitude({{Qx,Qy,Qz},Qw})
when is_float(Qx),is_float(Qy),is_float(Qz),is_float(Qw) ->
- math:sqrt(Qx*Qx+Qy*Qy+Qz*Qz+Qw*Qw).
-
+ MagSqr = Qx*Qx+Qy*Qy+Qz*Qz+Qw*Qw,
+ if MagSqr > 0.99999, MagSqr < 1.00001 ->
+ 1.0;
+ true ->
+ math:sqrt(MagSqr)
+ end.
conjugate({{Qx,Qy,Qz},Qw})
when is_float(Qx), is_float(Qy), is_float(Qz), is_float(Qw) ->
{{-Qx,-Qy,-Qz},Qw}.
@@ -46,9 +52,11 @@ inverse(Q) ->
norm(Q = {{Qx,Qy,Qz},Qw})
when is_float(Qx),is_float(Qy),is_float(Qz),is_float(Qw) ->
M = magnitude(Q),
- case catch {{Qx/M,Qy/M,Qz/M},Qw/M} of
- {'EXIT', _} -> {{0.0,0.0,0.0},0.0};
- R -> R
+ if M =:= 1.0 -> Q;
+ M < 0.00001 -> {{0.0,0.0,0.0},0.0};
+ true ->
+ IM = 1/M,
+ {{Qx*IM,Qy*IM,Qz*IM},Qw*IM}
end.
add({{X1,Y1,Z1},W1}, {{X2,Y2,Z2},W2})
@@ -60,6 +68,27 @@ scale({{Qx,Qy,Qz},Qw}, S)
when is_float(Qx),is_float(Qy),is_float(Qz),is_float(Qw), is_float(S) ->
{{Qx*S,Qy*S,Qz*S},Qw*S}.
+slerp(Q0={{X0,Y0,Z0},W0}, {{X1,Y1,Z1},W1}, T)
+ when is_float(X1),is_float(Y1),is_float(Z1),is_float(W1),
+ is_float(X0),is_float(Y0),is_float(Z0),is_float(W0),
+ is_float(T) ->
+ HalfCosTheta = X0*X1+Y0*Y1+Z0*Z1+W0*W1,
+ if abs(HalfCosTheta) >= 0.9999 ->
+ Q0;
+ true ->
+ HalfTheta = math:acos(HalfCosTheta),
+ HalfSinTheta = math:sin(HalfTheta),
+ if abs(HalfSinTheta) < 0.00001 ->
+ R0 = 1.0 - T,
+ R1 = T,
+ {{X0*R0+X1*R1, Y0*R0+Y1*R1, Z0*R0+Z1*R1},W0*R0+W1*R1};
+ true ->
+ R0 = math:sin((1.0 - T)*HalfTheta) / HalfSinTheta,
+ R1 = math:sin(T*HalfTheta) / HalfSinTheta,
+ {{X0*R0+X1*R1, Y0*R0+Y1*R1, Z0*R0+Z1*R1},W0*R0+W1*R1}
+ end
+ end.
+
mul([H|R]) ->
mmult(R, H).
@@ -117,6 +146,49 @@ from_rotation_matrix({M0, M4, M8,
from_rotation_matrix(M) when size(M) =:= 16 ->
from_rotation_matrix(e3d_mat:compress(M)).
+%% From/to euler
+%% From http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm
+%% Note: We rotate X first then Y and last Z as in:
+%% RX = e3d_q:from_angle_axis_rad(X, ?X),
+%% RY = e3d_q:from_angle_axis_rad(Y, ?Y),
+%% RZ = e3d_q:from_angle_axis_rad(Z, ?Z),
+%% e3d_q:mul(RZ, e3d_q:mul(RY, RX)).
+%%
+from_euler_rad({X, Y, Z}) ->
+ from_euler_rad(X, Y, Z).
+
+from_euler_rad(RX, RY, RZ) ->
+ RX2 = RX*0.5, RY2 = RY*0.5, RZ2 = RZ*0.5,
+ CX = math:cos(RX2),
+ SX = math:sin(RX2),
+ CY = math:cos(RY2),
+ SY = math:sin(RY2),
+ CZ = math:cos(RZ2),
+ SZ = math:sin(RZ2),
+ CXCY = CX*CY, CXSY = CX*SY,
+ SXSY = SX*SY, SXCY = SX*CY,
+ W = CXCY*CZ + SXSY*SZ,
+ X = SXCY*CZ - CXSY*SZ,
+ Y = CXSY*CZ + SXCY*SZ,
+ Z = CXCY*SZ - SXSY*CZ,
+ {{X,Y,Z},W}.
+
+to_euler_rad({{P1,P2,P3}, P0}) ->
+ P1P1 = P1*P1, P2P2 = P2*P2, P3P3 = P3*P3, P0P0 = P0*P0,
+ Unit = P1P1+P2P2+P3P3+P0P0,
+ Test = P0*P2 - P1*P3,
+ Limit = 0.4999*Unit,
+ if Test > Limit -> %% singularity at north pole
+ {math:atan2(P1,P0), math:pi()/2, 0.0};
+ Test < -Limit -> %% singularity at south pole
+ {math:atan2(P1,P0), -math:pi()/2, 0.0};
+ true ->
+ RX = math:atan2(2.0*(P0*P1+P2*P3), 1.0-2.0*(P1P1+P2P2)),
+ RY = math:asin(2.0*Test),
+ RZ = math:atan2(2.0*(P0*P3+P1*P2), 1.0-2.0*(P2P2+P3P3)),
+ {RX,RY,RZ}
+ end.
+
%% The Axis must be a unit-length vector.
from_angle_axis(Angle, Axis) ->
HalfAngle = Angle*(math:pi()/180.0/2.0),
@@ -125,6 +197,13 @@ from_angle_axis(Angle, Axis) ->
{X,Y,Z} = Axis,
{{X*Sin,Y*Sin,Z*Sin},Cos}.
+from_angle_axis_rad(Angle, Axis) ->
+ HalfAngle = Angle*0.5,
+ Sin = math:sin(HalfAngle),
+ Cos = math:cos(HalfAngle),
+ {X,Y,Z} = Axis,
+ {{X*Sin,Y*Sin,Z*Sin},Cos}.
+
to_angle_axis(Q) ->
{{Qx,Qy,Qz},Qw} = norm(Q),
Cos = Qw,
@@ -133,9 +212,23 @@ to_angle_axis(Q) ->
Sin = if
abs(Sin0) < 0.000005 -> 1.0;
true -> Sin0
- end,
+ end,
{Angle,{Qx/Sin,Qy/Sin,Qz/Sin}}.
+%% The Axis must be a unit-length vector.
+from_compact(R={Rx,Ry,Rz}) ->
+ T = 1.0 - (Rx * Rx) - (Ry * Ry) - (Rz * Rz),
+ case T < 0.0 of
+ true -> {R, 0.0};
+ false -> {R, -math:sqrt(T)}
+ end.
+
+to_compact(Q) ->
+ case norm(Q) of
+ {Vec, W} when W < 0.0 -> Vec;
+ {{Rx,Ry,Rz}, _} -> {-Rx,-Ry,-Rz}
+ end.
+
%% vec_rotate(Vec, Q)
%% Rotate a vector or point using quaternion Q.
vec_rotate({X2,Y2,Z2}, {{X1,Y1,Z1},W1})
View
12 e3d/e3d_vec.erl
@@ -13,8 +13,9 @@
-module(e3d_vec).
--export([zero/0,is_zero/1,add/1,add/2,add_prod/3,sub/1,sub/2,norm_sub/2,mul/2,
- divide/2,neg/1,dot/2,cross/2,len/1,dist/2,dist_sqr/2,
+-export([zero/0,is_zero/1,add/1,add/2,add_prod/3,sub/1,sub/2,lerp/3,
+ norm_sub/2,mul/2,divide/2,neg/1,dot/2,cross/2,
+ len/1,dist/2,dist_sqr/2,
norm/1,norm/3,normal/3,normal/1,average/1,average/2,average/4,
bounding_box/1,area/3,degrees/2,plane/3,plane_side/2,plane_dist/2]).
@@ -98,6 +99,13 @@ cross({V10,V11,V12}, {V20,V21,V22})
len({X,Y,Z}) when is_float(X), is_float(Y), is_float(Z) ->
math:sqrt(X*X+Y*Y+Z*Z).
+-spec lerp(e3d_vector(), e3d_vector(), float()) -> e3d_vector().
+
+lerp({V10,V11,V12}, {V20,V21,V22}, T)
+ when is_float(V10), is_float(V11), is_float(V12),
+ is_float(V20), is_float(V21), is_float(V22) ->
+ {V10+(V20-V10)*T, V11+(V21-V11)*T, V12+(V22-V12)*T}.
+
-spec dist(e3d_vector(), e3d_vector()) -> float().
dist({V10,V11,V12}, {V20,V21,V22}) when is_float(V10), is_float(V11), is_float(V12),
View
1,899 src/array.erl
@@ -1,1899 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2011. All Rights Reserved.
-%%
-%% The contents of this file are subject to the Erlang Public License,
-%% Version 1.1, (the "License"); you may not use this file except in
-%% compliance with the License. You should have received a copy of the
-%% Erlang Public License along with this software. If not, it can be
-%% retrieved online at http://www.erlang.org/.
-%%
-%% Software distributed under the License is distributed on an "AS IS"
-%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
-%% the License for the specific language governing rights and limitations
-%% under the License.
-%%
-%% %CopyrightEnd%
-%%
-%% @author Richard Carlsson <richardc@it.uu.se>
-%% @author Dan Gudmundsson <dgud@erix.ericsson.se>
-%% @version 1.0
-
-%% @doc Functional, extendible arrays. Arrays can have fixed size, or
-%% can grow automatically as needed. A default value is used for entries
-%% that have not been explicitly set.
-%%
-%% Arrays uses <b>zero</b> based indexing. This is a deliberate design
-%% choice and differs from other erlang datastructures, e.g. tuples.
-%%
-%% Unless specified by the user when the array is created, the default
-%% value is the atom `undefined'. There is no difference between an
-%% unset entry and an entry which has been explicitly set to the same
-%% value as the default one (cf. {@link reset/2}). If you need to
-%% differentiate between unset and set entries, you must make sure that
-%% the default value cannot be confused with the values of set entries.
-%%
-%% The array never shrinks automatically; if an index `I' has been used
-%% successfully to set an entry, all indices in the range [0,`I'] will
-%% stay accessible unless the array size is explicitly changed by
-%% calling {@link resize/2}.
-%%
-%% Examples:
-%% ```
-%% %% Create a fixed-size array with entries 0-9 set to 'undefined'
-%% A0 = array:new(10).
-%% 10 = array:size(A0).
-%%
-%% %% Create an extendible array and set entry 17 to 'true',
-%% %% causing the array to grow automatically
-%% A1 = array:set(17, true, array:new()).
-%% 18 = array:size(A1).
-%%
-%% %% Read back a stored value
-%% true = array:get(17, A1).
-%%
-%% %% Accessing an unset entry returns the default value
-%% undefined = array:get(3, A1).
-%%
-%% %% Accessing an entry beyond the last set entry also returns the
-%% %% default value, if the array does not have fixed size
-%% undefined = array:get(18, A1).
-%%
-%% %% "sparse" functions ignore default-valued entries
-%% A2 = array:set(4, false, A1).
-%% [{4, false}, {17, true}] = array:sparse_to_orddict(A2).
-%%
-%% %% An extendible array can be made fixed-size later
-%% A3 = array:fix(A2).
-%%
-%% %% A fixed-size array does not grow automatically and does not
-%% %% allow accesses beyond the last set entry
-%% {'EXIT',{badarg,_}} = (catch array:set(18, true, A3)).
-%% {'EXIT',{badarg,_}} = (catch array:get(18, A3)).
-%% '''
-
-%% @type array(). A functional, extendible array. The representation is
-%% not documented and is subject to change without notice. Note that
-%% arrays cannot be directly compared for equality.
-
--module(array).
-
--export([new/0, new/1, new/2, is_array/1, set/3, get/2, size/1,
- sparse_size/1, default/1, reset/2, to_list/1, sparse_to_list/1,
- from_list/1, from_list/2, to_orddict/1, sparse_to_orddict/1,
- from_orddict/1, from_orddict/2, map/2, sparse_map/2, foldl/3,
- foldr/3, sparse_foldl/3, sparse_foldr/3, fix/1, relax/1, is_fix/1,
- resize/1, resize/2]).
-
-
--ifdef(TEST).
--include_lib("eunit/include/eunit.hrl").
--endif.
-
-
-%% Developers:
-%%
-%% For OTP devs: Both tests and documentation is extracted from this
-%% file, keep and update this file,
-%% test are extracted with array_SUITE:extract_tests().
-%% Doc with docb_gen array.erl
-%%
-%% The key to speed is to minimize the number of tests, on
-%% large input. Always make the most probable path as short as possible.
-%% In particular, keep in mind that for large trees, the probability of
-%% a leaf node is small relative to that of an internal node.
-%%
-%% If you try to tweak the set_1 and get_1 loops: Measure, look at the
-%% generated Beam code, and measure again! The argument order matters!
-
-
-%% Representation:
-%%
-%% A tree is either a leaf, with LEAFSIZE elements (the "base"), an
-%% internal node with LEAFSIZE+1 elements, or an unexpanded tree,
-%% represented by a single integer: the number of elements that may be
-%% stored in the tree when it is expanded. The last element of an
-%% internal node caches the number of elements that may be stored in
-%% each of its subtrees.
-%%
-%% Note that to update an entry in a tree of height h = log[b] n, the
-%% total number of written words is (b+1)+(h-1)*(b+2), since tuples use
-%% a header word on the heap. 4 is the optimal base for minimizing the
-%% number of words written, but causes higher trees, which takes time.
-%% The best compromise between speed and memory usage seems to lie
-%% around 8-10. Measurements indicate that the optimum base for speed is
-%% 24 - above that, it gets slower again due to the high memory usage.
-%% Base 10 is a good choice, giving 2/3 of the possible speedup from
-%% base 4, but only using 1/3 more memory. (Base 24 uses 65% more memory
-%% per write than base 10, but the speedup is only 21%.)
-
--define(DEFAULT, undefined).
--define(LEAFSIZE, 10). % the "base"
--define(NODESIZE, ?LEAFSIZE). % (no reason to have a different size)
--define(NODEPATTERN(S), {_,_,_,_,_,_,_,_,_,_,S}). % NODESIZE+1 elements!
--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))).
--define(NODELEAFS, ?NODESIZE*?LEAFSIZE).
-
-%% These make the code a little easier to experiment with.
-%% It turned out that using shifts (when NODESIZE=2^n) was not faster.
--define(reduce(X), ((X) div (?NODESIZE))).
--define(extend(X), ((X) * (?NODESIZE))).
-
-%%--------------------------------------------------------------------------
-
--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
- }).
-%% A declaration equivalent to the following one is hard-coded in erl_types.
-%% That declaration contains hard-coded information about the #array{}
-%% structure and the types of its fields. So, please make sure that any
-%% changes to its structure are also propagated to erl_types.erl.
-%%
-%% -opaque array() :: #array{}.
-
-%%
-%% Types
-%%
-
--type array_indx() :: non_neg_integer().
-
--type array_opt() :: 'fixed' | non_neg_integer()
- | {'default', term()} | {'fixed', boolean()}
- | {'size', non_neg_integer()}.
--type array_opts() :: array_opt() | [array_opt()].
-
--type indx_pair() :: {array_indx(), term()}.
--type indx_pairs() :: [indx_pair()].
-
-%%--------------------------------------------------------------------------
-
-%% @spec () -> array()
-%% @doc Create a new, extendible array with initial size zero.
-%% @equiv new([])
-%%
-%% @see new/1
-%% @see new/2
-
--spec new() -> array().
-
-new() ->
- new([]).
-
-%% @spec (Options::term()) -> array()
-%% @doc Create a new array according to the given options. By default,
-%% the array is extendible and has initial size zero. Array indices
-%% start at 0.
-%%
-%% `Options' is a single term or a list of terms, selected from the
-%% following:
-%% <dl>
-%% <dt>`N::integer()' or `{size, N::integer()}'</dt>
-%% <dd>Specifies the initial size of the array; this also implies
-%% `{fixed, true}'. If `N' is not a nonnegative integer, the call
-%% fails with reason `badarg'.</dd>
-%% <dt>`fixed' or `{fixed, true}'</dt>
-%% <dd>Creates a fixed-size array; see also {@link fix/1}.</dd>
-%% <dt>`{fixed, false}'</dt>
-%% <dd>Creates an extendible (non fixed-size) array.</dd>
-%% <dt>`{default, Value}'</dt>
-%% <dd>Sets the default value for the array to `Value'.</dd>
-%% </dl>
-%% Options are processed in the order they occur in the list, i.e.,
-%% later options have higher precedence.
-%%
-%% The default value is used as the value of uninitialized entries, and
-%% cannot be changed once the array has been created.
-%%
-%% Examples:
-%% ```array:new(100)''' creates a fixed-size array of size 100.
-%% ```array:new({default,0})''' creates an empty, extendible array
-%% whose default value is 0.
-%% ```array:new([{size,10},{fixed,false},{default,-1}])''' creates an
-%% extendible array with initial size 10 whose default value is -1.
-%%
-%% @see new/0
-%% @see new/2
-%% @see set/3
-%% @see get/2
-%% @see from_list/2
-%% @see fix/1
-
--spec new(array_opts()) -> array().
-
-new(Options) ->
- new_0(Options, 0, false).
-
-%% @spec (Size::integer(), Options::term()) -> array()
-%% @doc Create a new array according to the given size and options. If
-%% `Size' is not a nonnegative integer, the call fails with reason
-%% `badarg'. By default, the array has fixed size. Note that any size
-%% specifications in `Options' will override the `Size' parameter.
-%%
-%% If `Options' is a list, this is simply equivalent to `new([{size,
-%% Size} | Options]', otherwise it is equivalent to `new([{size, Size} |
-%% [Options]]'. However, using this function directly is more efficient.
-%%
-%% Example:
-%% ```array:new(100, {default,0})''' creates a fixed-size array of size
-%% 100, whose default value is 0.
-%%
-%% @see new/1
-
--spec new(non_neg_integer(), array_opts()) -> array().
-
-new(Size, Options) when is_integer(Size), Size >= 0 ->
- new_0(Options, Size, true);
-new(_, _) ->
- error(badarg).
-
-new_0(Options, Size, Fixed) when is_list(Options) ->
- new_1(Options, Size, Fixed, ?DEFAULT);
-new_0(Options, Size, Fixed) ->
- new_1([Options], Size, Fixed, ?DEFAULT).
-
-new_1([fixed | Options], Size, _, Default) ->
- new_1(Options, Size, true, Default);
-new_1([{fixed, Fixed} | Options], Size, _, Default)
- when is_boolean(Fixed) ->
- new_1(Options, Size, Fixed, Default);
-new_1([{default, Default} | Options], Size, Fixed, _) ->
- new_1(Options, Size, Fixed, Default);
-new_1([{size, Size} | Options], _, _, Default)
- when is_integer(Size), Size >= 0 ->
- new_1(Options, Size, true, Default);
-new_1([Size | Options], _, _, Default)
- when is_integer(Size), Size >= 0 ->
- new_1(Options, Size, true, Default);
-new_1([], Size, Fixed, Default) ->
- new(Size, Fixed, Default);
-new_1(_Options, _Size, _Fixed, _Default) ->
- error(badarg).
-
-new(Size, Fixed, Default) ->
- E = find_max(Size - 1, ?LEAFSIZE),
- M = if Fixed -> 0;
- true -> E
- end,
- #array{size = Size, max = M, default = Default, elements = E}.
-
--spec find_max(integer(), integer()) -> integer().
-
-find_max(I, M) when I >= M ->
- find_max(I, ?extend(M));
-find_max(_I, M) ->
- M.
-
-
-%% @spec (X::term()) -> boolean()
-%% @doc Returns `true' if `X' appears to be an array, otherwise `false'.
-%% Note that the check is only shallow; there is no guarantee that `X'
-%% is a well-formed array representation even if this function returns
-%% `true'.
-
--spec is_array(term()) -> boolean().
-
-is_array(#array{size = Size, max = Max})
- when is_integer(Size), is_integer(Max) ->
- true;
-is_array(_) ->
- false.
-
-
-%% @spec (array()) -> integer()
-%% @doc Get the number of entries in the array. Entries are numbered
-%% from 0 to `size(Array)-1'; hence, this is also the index of the first
-%% entry that is guaranteed to not have been previously set.
-%% @see set/3
-%% @see sparse_size/1
-
--spec size(array()) -> non_neg_integer().
-
-size(#array{size = N}) -> N;
-size(_) -> error(badarg).
-
-
-%% @spec (array()) -> term()
-%% @doc Get the value used for uninitialized entries.
-%%
-%% @see new/2
-
--spec default(array()) -> term().
-
-default(#array{default = D}) -> D;
-default(_) -> error(badarg).
-
-
--ifdef(EUNIT).
-new_test_() ->
- N0 = ?LEAFSIZE,
- N01 = N0+1,
- N1 = ?NODESIZE*N0,
- N11 = N1+1,
- N2 = ?NODESIZE*N1,
- [?_test(new()),
-
- ?_test(new([])),
- ?_test(new(10)),
- ?_test(new({size,10})),
- ?_test(new(fixed)),
- ?_test(new({fixed,true})),
- ?_test(new({fixed,false})),
- ?_test(new({default,undefined})),
- ?_test(new([{size,100},{fixed,false},{default,undefined}])),
- ?_test(new([100,fixed,{default,0}])),
-
- ?_assert(new() =:= new([])),
- ?_assert(new() =:= new([{size,0},{default,undefined},{fixed,false}])),
- ?_assert(new() =:= new(0, {fixed,false})),
- ?_assert(new(fixed) =:= new(0)),
- ?_assert(new(fixed) =:= new(0, [])),
- ?_assert(new(10) =:= new([{size,0},{size,5},{size,10}])),
- ?_assert(new(10) =:= new(0, {size,10})),
- ?_assert(new(10, []) =:= new(10, [{default,undefined},{fixed,true}])),
-
- ?_assertError(badarg, new(-1)),
- ?_assertError(badarg, new(10.0)),
- ?_assertError(badarg, new(undefined)),
- ?_assertError(badarg, new([undefined])),
- ?_assertError(badarg, new([{default,0} | fixed])),
-
- ?_assertError(badarg, new(-1, [])),
- ?_assertError(badarg, new(10.0, [])),
- ?_assertError(badarg, new(undefined, [])),
-
- ?_assertMatch(#array{size=0,max=N0,default=undefined,elements=N0},
- new()),
- ?_assertMatch(#array{size=0,max=0,default=undefined,elements=N0},
- new(fixed)),
- ?_assertMatch(#array{size=N0,max=N0,elements=N0},
- new(N0, {fixed,false})),
- ?_assertMatch(#array{size=N01,max=N1,elements=N1},
- new(N01, {fixed,false})),
- ?_assertMatch(#array{size=N1,max=N1,elements=N1},
- new(N1, {fixed,false})),
- ?_assertMatch(#array{size=N11,max=N2,elements=N2},
- new(N11, {fixed,false})),
- ?_assertMatch(#array{size=N2, max=N2, default=42,elements=N2},
- new(N2, [{fixed,false},{default,42}])),
-
- ?_assert(0 =:= array:size(new())),
- ?_assert(17 =:= array:size(new(17))),
- ?_assert(100 =:= array:size(array:set(99,0,new()))),
- ?_assertError(badarg, array:size({bad_data,gives_error})),
-
- ?_assert(undefined =:= default(new())),
- ?_assert(4711 =:= default(new({default,4711}))),
- ?_assert(0 =:= default(new(10, {default,0}))),
- ?_assertError(badarg, default({bad_data,gives_error})),
-
- ?_assert(is_array(new())),
- ?_assert(false =:= is_array({foobar, 23, 23})),
- ?_assert(false =:= is_array(#array{size=bad})),
- ?_assert(false =:= is_array(#array{max=bad})),
- ?_assert(is_array(new(10))),
- ?_assert(is_array(new(10, {fixed,false})))
- ].
--endif.
-
-
-%% @spec (array()) -> array()
-%% @doc Fix the size of the array. This prevents it from growing
-%% automatically upon insertion; see also {@link set/3}.
-%% @see relax/1
-
--spec fix(array()) -> array().
-
-fix(#array{}=A) ->
- A#array{max = 0}.
-
-
-%% @spec (array()) -> boolean()
-%% @doc Check if the array has fixed size.
-%% Returns `true' if the array is fixed, otherwise `false'.
-%% @see fix/1
-
--spec is_fix(array()) -> boolean().
-
-is_fix(#array{max = 0}) -> true;
-is_fix(#array{}) -> false.
-
-
--ifdef(EUNIT).
-fix_test_() ->
- [?_assert(is_array(fix(new()))),
- ?_assert(fix(new()) =:= new(fixed)),
-
- ?_assertNot(is_fix(new())),
- ?_assertNot(is_fix(new([]))),
- ?_assertNot(is_fix(new({fixed,false}))),
- ?_assertNot(is_fix(new(10, {fixed,false}))),
- ?_assert(is_fix(new({fixed,true}))),
- ?_assert(is_fix(new(fixed))),
- ?_assert(is_fix(new(10))),
- ?_assert(is_fix(new(10, []))),
- ?_assert(is_fix(new(10, {fixed,true}))),
- ?_assert(is_fix(fix(new()))),
- ?_assert(is_fix(fix(new({fixed,false})))),
-
- ?_test(set(0, 17, new())),
- ?_assertError(badarg, set(0, 17, new(fixed))),
- ?_assertError(badarg, set(1, 42, fix(set(0, 17, new())))),
-
- ?_test(set(9, 17, new(10))),
- ?_assertError(badarg, set(10, 17, new(10))),
- ?_assertError(badarg, set(10, 17, fix(new(10, {fixed,false}))))
- ].
--endif.
-
-
-%% @spec (array()) -> array()
-%% @doc Make the array resizable. (Reverses the effects of {@link
-%% fix/1}.)
-%% @see fix/1
-
--spec relax(array()) -> array().
-
-relax(#array{size = N}=A) ->
- A#array{max = find_max(N-1, ?LEAFSIZE)}.
-
-
--ifdef(EUNIT).
-relax_test_() ->
- [?_assert(is_array(relax(new(fixed)))),
- ?_assertNot(is_fix(relax(fix(new())))),
- ?_assertNot(is_fix(relax(new(fixed)))),
-
- ?_assert(new() =:= relax(new(fixed))),
- ?_assert(new() =:= relax(new(0))),
- ?_assert(new(17, {fixed,false}) =:= relax(new(17))),
- ?_assert(new(100, {fixed,false})
- =:= relax(fix(new(100, {fixed,false}))))
- ].
--endif.
-
-
-%% @spec (integer(), array()) -> array()
-%% @doc Change the size of the array. If `Size' is not a nonnegative
-%% integer, the call fails with reason `badarg'. If the given array has
-%% fixed size, the resulting array will also have fixed size.
-
--spec resize(non_neg_integer(), array()) -> array().
-
-resize(Size, #array{size = N, max = M, elements = E}=A)
- when is_integer(Size), Size >= 0 ->
- if Size > N ->
- {E1, M1} = grow(Size-1, E,
- if M > 0 -> M;
- true -> find_max(N-1, ?LEAFSIZE)
- end),
- A#array{size = Size,
- max = if M > 0 -> M1;
- true -> M
- end,
- elements = E1};
- Size < N ->
- %% TODO: shrink physical representation when shrinking the array
- A#array{size = Size};
- true ->
- A
- end;
-resize(_Size, _) ->
- error(badarg).
-
-
-%% @spec (array()) -> array()
-
-%% @doc Change the size of the array to that reported by {@link
-%% sparse_size/1}. If the given array has fixed size, the resulting
-%% array will also have fixed size.
-%% @equiv resize(sparse_size(Array), Array)
-%% @see resize/2
-%% @see sparse_size/1
-
--spec resize(array()) -> array().
-
-resize(Array) ->
- resize(sparse_size(Array), Array).
-
-
--ifdef(EUNIT).
-resize_test_() ->
- [?_assert(resize(0, new()) =:= new()),
- ?_assert(resize(99, new(99)) =:= new(99)),
- ?_assert(resize(99, relax(new(99))) =:= relax(new(99))),
- ?_assert(is_fix(resize(100, new(10)))),
- ?_assertNot(is_fix(resize(100, relax(new(10))))),
-
- ?_assert(array:size(resize(100, new())) =:= 100),
- ?_assert(array:size(resize(0, new(100))) =:= 0),
- ?_assert(array:size(resize(99, new(10))) =:= 99),
- ?_assert(array:size(resize(99, new(1000))) =:= 99),
-
- ?_assertError(badarg, set(99, 17, new(10))),
- ?_test(set(99, 17, resize(100, new(10)))),
- ?_assertError(badarg, set(100, 17, resize(100, new(10)))),
-
- ?_assert(array:size(resize(new())) =:= 0),
- ?_assert(array:size(resize(new(8))) =:= 0),
- ?_assert(array:size(resize(array:set(7, 0, new()))) =:= 8),
- ?_assert(array:size(resize(array:set(7, 0, new(10)))) =:= 8),
- ?_assert(array:size(resize(array:set(99, 0, new(10,{fixed,false}))))
- =:= 100),
- ?_assert(array:size(resize(array:set(7, undefined, new()))) =:= 0),
- ?_assert(array:size(resize(array:from_list([1,2,3,undefined])))
- =:= 3),
- ?_assert(array:size(
- resize(array:from_orddict([{3,0},{17,0},{99,undefined}])))
- =:= 18),
- ?_assertError(badarg, resize(foo, bad_argument))
- ].
--endif.
-
-
-%% @spec (integer(), term(), array()) -> array()
-%% @doc Set entry `I' of the array to `Value'. If `I' is not a
-%% nonnegative integer, or if the array has fixed size and `I' is larger
-%% than the maximum index, the call fails with reason `badarg'.
-%%
-%% If the array does not have fixed size, and `I' is greater than
-%% `size(Array)-1', the array will grow to size `I+1'.
-%%
-%% @see get/2
-%% @see reset/2
-
--spec set(array_indx(), term(), array()) -> array().
-
-set(I, Value, #array{size = N, max = M, default = D, elements = E}=A)
- when is_integer(I), I >= 0 ->
- if I < N ->
- A#array{elements = set_1(I, E, Value, D)};
- I < M ->
- %% (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 ->
- {E1, M1} = grow(I, E, M),
- A#array{size = I+1, max = M1,
- elements = set_1(I, E1, Value, D)};
- true ->
- error(badarg)
- end;
-set(_I, _V, _A) ->
- 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).
-
-
-%% @spec (integer(), array()) -> term()
-%% @doc Get the value of entry `I'. If `I' is not a nonnegative
-%% integer, or if the array has fixed size and `I' is larger than the
-%% maximum index, the call fails with reason `badarg'.
-%%
-%% If the array does not have fixed size, this function will return the
-%% default value for any index `I' greater than `size(Array)-1'.
-
-%% @see set/3
-
--spec get(array_indx(), array()) -> term().
-
-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 ->
- error(badarg)
- end;
-get(_I, _A) ->
- 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).
-
-
-%% @spec (integer(), array()) -> array()
-%% @doc Reset entry `I' to the default value for the array. This is
-%% equivalent to `set(I, default(Array), Array)', and hence may cause
-%% the array to grow in size, but will not shrink it. Shrinking can be
-%% done explicitly by calling {@link resize/2}.
-%%
-%% If `I' is not a nonnegative integer, or if the array has fixed size
-%% and `I' is larger than the maximum index, the call fails with reason
-%% `badarg'; cf. {@link set/3}
-%%
-%% @see new/2
-%% @see set/3
-
-%% TODO: a reset_range function
-
--spec reset(array_indx(), array()) -> array().
-
-reset(I, Array) ->
- set(I, Array#array.default, Array).
-
-
--ifdef(EUNIT).
-set_get_test_() ->
- N0 = ?LEAFSIZE,
- N1 = ?NODESIZE*N0,
- [?_assert(array:get(0, new()) =:= undefined),
- ?_assert(array:get(1, new()) =:= undefined),
- ?_assert(array:get(99999, new()) =:= undefined),
-
- ?_assert(array:get(0, new(1)) =:= undefined),
- ?_assert(array:get(0, new(1,{default,0})) =:= 0),
- ?_assert(array:get(9, new(10)) =:= undefined),
-
- ?_assertError(badarg, array:get(0, new(fixed))),
- ?_assertError(badarg, array:get(1, new(1))),
- ?_assertError(badarg, array:get(-1, new(1))),
- ?_assertError(badarg, array:get(10, new(10))),
- ?_assertError(badarg, array:set(-1, foo, new(10))),
- ?_assertError(badarg, array:set(10, foo, no_array)),
-
- ?_assert(array:size(set(0, 17, new())) =:= 1),
- ?_assert(array:size(set(N1-1, 17, new())) =:= N1),
- ?_assert(array:size(set(0, 42, set(0, 17, new()))) =:= 1),
- ?_assert(array:size(set(9, 42, set(0, 17, new()))) =:= 10),
-
- ?_assert(array:get(0, set(0, 17, new())) =:= 17),
- ?_assert(array:get(0, set(1, 17, new())) =:= undefined),
- ?_assert(array:get(1, set(1, 17, new())) =:= 17),
-
- ?_assert(array:get(0, fix(set(0, 17, new()))) =:= 17),
- ?_assertError(badarg, array:get(1, fix(set(0, 17, new())))),
-
- ?_assert(array:get(N1-2, set(N1-1, 17, new())) =:= undefined),
- ?_assert(array:get(N1-1, set(N1-1, 17, new())) =:= 17),
- ?_assertError(badarg, array:get(N1, fix(set(N1-1, 17, new())))),
-
- ?_assert(array:get(0, set(0, 42, set(0, 17, new()))) =:= 42),
-
- ?_assert(array:get(0, reset(0, new())) =:= undefined),
- ?_assert(array:get(0, reset(0, set(0, 17, new()))) =:= undefined),
- ?_assert(array:get(0, reset(0, new({default,42}))) =:= 42),
- ?_assert(array:get(0, reset(0, set(0, 17, new({default,42}))))
- =:= 42)
- ].
--endif.
-
-
-%% @spec (array()) -> list()
-%% @doc Converts the array to a list.
-%%
-%% @see from_list/2
-%% @see sparse_to_list/1
-
--spec to_list(array()) -> list().
-
-to_list(#array{size = 0}) ->
- [];
-to_list(#array{size = N, elements = E, default = D}) ->
- to_list_1(E, D, N - 1);
-to_list(_) ->
- error(badarg).
-
-%% this part handles the rightmost subtrees
-
-to_list_1(E=?NODEPATTERN(S), D, I) ->
- N = I div S,
- to_list_3(N, D, to_list_1(element(N+1, E), D, I rem S), E);
-to_list_1(E, D, I) when is_integer(E) ->
- push(I+1, D, []);
-to_list_1(E, _D, I) ->
- push_tuple(I+1, E, []).
-
-%% this part handles full trees only
-
-to_list_2(E=?NODEPATTERN(_S), D, L) ->
- to_list_3(?NODESIZE, D, L, E);
-to_list_2(E, D, L) when is_integer(E) ->
- push(E, D, L);
-to_list_2(E, _D, L) ->
- push_tuple(?LEAFSIZE, E, L).
-
-to_list_3(0, _D, L, _E) ->
- L;
-to_list_3(N, D, L, E) ->
- to_list_3(N-1, D, to_list_2(element(N, E), D, L), E).
-
-push(0, _E, L) ->
- L;
-push(N, E, L) ->
- push(N - 1, E, [E | L]).
-
-push_tuple(0, _T, L) ->
- L;
-push_tuple(N, T, L) ->
- push_tuple(N - 1, T, [element(N, T) | L]).
-
-
--ifdef(EUNIT).
-to_list_test_() ->
- N0 = ?LEAFSIZE,
- [?_assert([] =:= to_list(new())),
- ?_assert([undefined] =:= to_list(new(1))),
- ?_assert([undefined,undefined] =:= to_list(new(2))),
- ?_assert(lists:duplicate(N0,0) =:= to_list(new(N0,{default,0}))),
- ?_assert(lists:duplicate(N0+1,1) =:= to_list(new(N0+1,{default,1}))),
- ?_assert(lists:duplicate(N0+2,2) =:= to_list(new(N0+2,{default,2}))),
- ?_assert(lists:duplicate(666,6) =:= to_list(new(666,{default,6}))),
- ?_assert([1,2,3] =:= to_list(set(2,3,set(1,2,set(0,1,new()))))),
- ?_assert([3,2,1] =:= to_list(set(0,3,set(1,2,set(2,1,new()))))),
- ?_assert([1|lists:duplicate(N0-2,0)++[1]] =:=
- to_list(set(N0-1,1,set(0,1,new({default,0}))))),
- ?_assert([1|lists:duplicate(N0-1,0)++[1]] =:=
- to_list(set(N0,1,set(0,1,new({default,0}))))),
- ?_assert([1|lists:duplicate(N0,0)++[1]] =:=
- to_list(set(N0+1,1,set(0,1,new({default,0}))))),
- ?_assert([1|lists:duplicate(N0*3,0)++[1]] =:=
- to_list(set((N0*3)+1,1,set(0,1,new({default,0}))))),
- ?_assertError(badarg, to_list(no_array))
- ].
--endif.
-
-
-%% @spec (array()) -> list()
-%% @doc Converts the array to a list, skipping default-valued entries.
-%%
-%% @see to_list/1
-
--spec sparse_to_list(array()) -> list().
-
-sparse_to_list(#array{size = 0}) ->
- [];
-sparse_to_list(#array{size = N, elements = E, default = D}) ->
- sparse_to_list_1(E, D, N - 1);
-sparse_to_list(_) ->
- error(badarg).
-
-%% see to_list/1 for details
-
-sparse_to_list_1(E=?NODEPATTERN(S), D, I) ->
- N = I div S,
- sparse_to_list_3(N, D,
- sparse_to_list_1(element(N+1, E), D, I rem S),
- E);
-sparse_to_list_1(E, _D, _I) when is_integer(E) ->
- [];
-sparse_to_list_1(E, D, I) ->
- sparse_push_tuple(I+1, D, E, []).
-
-sparse_to_list_2(E=?NODEPATTERN(_S), D, L) ->
- sparse_to_list_3(?NODESIZE, D, L, E);
-sparse_to_list_2(E, _D, L) when is_integer(E) ->
- L;
-sparse_to_list_2(E, D, L) ->
- sparse_push_tuple(?LEAFSIZE, D, E, L).
-
-sparse_to_list_3(0, _D, L, _E) ->
- L;
-sparse_to_list_3(N, D, L, E) ->
- sparse_to_list_3(N-1, D, sparse_to_list_2(element(N, E), D, L), E).
-
-sparse_push_tuple(0, _D, _T, L) ->
- L;
-sparse_push_tuple(N, D, T, L) ->
- case element(N, T) of
- D -> sparse_push_tuple(N - 1, D, T, L);
- E -> sparse_push_tuple(N - 1, D, T, [E | L])
- end.
-
-
--ifdef(EUNIT).
-sparse_to_list_test_() ->
- N0 = ?LEAFSIZE,
- [?_assert([] =:= sparse_to_list(new())),
- ?_assert([] =:= sparse_to_list(new(1))),
- ?_assert([] =:= sparse_to_list(new(1,{default,0}))),
- ?_assert([] =:= sparse_to_list(new(2))),
- ?_assert([] =:= sparse_to_list(new(2,{default,0}))),
- ?_assert([] =:= sparse_to_list(new(N0,{default,0}))),
- ?_assert([] =:= sparse_to_list(new(N0+1,{default,1}))),
- ?_assert([] =:= sparse_to_list(new(N0+2,{default,2}))),
- ?_assert([] =:= sparse_to_list(new(666,{default,6}))),
- ?_assert([1,2,3] =:= sparse_to_list(set(2,3,set(1,2,set(0,1,new()))))),
- ?_assert([3,2,1] =:= sparse_to_list(set(0,3,set(1,2,set(2,1,new()))))),
- ?_assert([0,1] =:= sparse_to_list(set(N0-1,1,set(0,0,new())))),
- ?_assert([0,1] =:= sparse_to_list(set(N0,1,set(0,0,new())))),
- ?_assert([0,1] =:= sparse_to_list(set(N0+1,1,set(0,0,new())))),
- ?_assert([0,1,2] =:= sparse_to_list(set(N0*10+1,2,set(N0*2+1,1,set(0,0,new()))))),
- ?_assertError(badarg, sparse_to_list(no_array))
- ].
--endif.
-
-
-%% @spec (list()) -> array()
-%% @equiv from_list(List, undefined)
-
--spec from_list(list()) -> array().
-
-from_list(List) ->
- from_list(List, undefined).
-
-%% @spec (list(), term()) -> array()
-%% @doc Convert a list to an extendible array. `Default' is used as the value
-%% for uninitialized entries of the array. If `List' is not a proper list,
-%% the call fails with reason `badarg'.
-%%
-%% @see new/2
-%% @see to_list/1
-
--spec from_list(list(), term()) -> array().
-
-from_list([], Default) ->
- new({default,Default});
-from_list(List, Default) when is_list(List) ->
- {E, N, M} = from_list_1(?LEAFSIZE, List, Default, 0, [], []),
- #array{size = N, max = M, default = Default, elements = E};
-from_list(_, _) ->
- error(badarg).
-
-%% Note: A cleaner but slower algorithm is to first take the length of
-%% the list and compute the max size of the final tree, and then
-%% decompose the list. The below algorithm is almost twice as fast,
-%% however.
-
-%% Building the leaf nodes (padding the last one as necessary) and
-%% counting the total number of elements.
-from_list_1(0, Xs, D, N, As, Es) ->
- E = list_to_tuple(lists:reverse(As)),
- case Xs of
- [] ->
- case Es of
- [] ->
- {E, N, ?LEAFSIZE};
- _ ->
- from_list_2_0(N, [E | Es], ?LEAFSIZE)
- end;
- [_|_] ->
- from_list_1(?LEAFSIZE, Xs, D, N, [], [E | Es]);
- _ ->
- error(badarg)
- end;
-from_list_1(I, Xs, D, N, As, Es) ->
- case Xs of
- [X | Xs1] ->
- from_list_1(I-1, Xs1, D, N+1, [X | As], Es);
- _ ->
- from_list_1(I-1, Xs, D, N, [D | As], Es)
- end.
-
-%% Building the internal nodes (note that the input is reversed).
-from_list_2_0(N, Es, S) ->
- from_list_2(?NODESIZE, pad((N-1) div S + 1, ?NODESIZE, S, Es),
- S, N, [S], []).
-
-from_list_2(0, Xs, S, N, As, Es) ->
- E = list_to_tuple(As),
- case Xs of
- [] ->
- case Es of
- [] ->
- {E, N, ?extend(S)};
- _ ->
- from_list_2_0(N, lists:reverse([E | Es]),
- ?extend(S))
- end;
- _ ->
- from_list_2(?NODESIZE, Xs, S, N, [S], [E | Es])
- end;
-from_list_2(I, [X | Xs], S, N, As, Es) ->
- from_list_2(I-1, Xs, S, N, [X | As], Es).
-
-
-%% left-padding a list Es with elements P to the nearest multiple of K
-%% elements from N (adding 0 to K-1 elements).
-pad(N, K, P, Es) ->
- push((K - (N rem K)) rem K, P, Es).
-
-
--ifdef(EUNIT).
-from_list_test_() ->
- N0 = ?LEAFSIZE,
- N1 = ?NODESIZE*N0,
- N2 = ?NODESIZE*N1,
- N3 = ?NODESIZE*N2,
- N4 = ?NODESIZE*N3,
- [?_assert(array:size(from_list([])) =:= 0),
- ?_assert(array:is_fix(from_list([])) =:= false),
- ?_assert(array:size(from_list([undefined])) =:= 1),
- ?_assert(array:is_fix(from_list([undefined])) =:= false),
- ?_assert(array:size(from_list(lists:seq(1,N1))) =:= N1),
- ?_assert(to_list(from_list(lists:seq(1,N0))) =:= lists:seq(1,N0)),
- ?_assert(to_list(from_list(lists:seq(1,N0+1))) =:= lists:seq(1,N0+1)),
- ?_assert(to_list(from_list(lists:seq(1,N0+2))) =:= lists:seq(1,N0+2)),
- ?_assert(to_list(from_list(lists:seq(1,N2))) =:= lists:seq(1,N2)),
- ?_assert(to_list(from_list(lists:seq(1,N2+1))) =:= lists:seq(1,N2+1)),
- ?_assert(to_list(from_list(lists:seq(0,N3))) =:= lists:seq(0,N3)),
- ?_assert(to_list(from_list(lists:seq(0,N4))) =:= lists:seq(0,N4)),
- ?_assertError(badarg, from_list([a,b,a,c|d])),
- ?_assertError(badarg, from_list(no_array))
- ].
--endif.
-
-
-%% @spec (array()) -> [{Index::integer(), Value::term()}]
-%% @doc Convert the array to an ordered list of pairs `{Index, Value}'.
-%%
-%% @see from_orddict/2
-%% @see sparse_to_orddict/1
-
--spec to_orddict(array()) -> indx_pairs().
-
-to_orddict(#array{size = 0}) ->
- [];
-to_orddict(#array{size = N, elements = E, default = D}) ->
- I = N - 1,
- to_orddict_1(E, I, D, I);
-to_orddict(_) ->
- error(badarg).
-
-%% see to_list/1 for comparison
-
-to_orddict_1(E=?NODEPATTERN(S), R, D, I) ->
- N = I div S,
- I1 = I rem S,
- to_orddict_3(N, R - I1 - 1, D,
- to_orddict_1(element(N+1, E), R, D, I1),
- E, S);
-to_orddict_1(E, R, D, I) when is_integer(E) ->
- push_pairs(I+1, R, D, []);
-to_orddict_1(E, R, _D, I) ->
- push_tuple_pairs(I+1, R, E, []).
-
-to_orddict_2(E=?NODEPATTERN(S), R, D, L) ->
- to_orddict_3(?NODESIZE, R, D, L, E, S);
-to_orddict_2(E, R, D, L) when is_integer(E) ->
- push_pairs(E, R, D, L);
-to_orddict_2(E, R, _D, L) ->
- push_tuple_pairs(?LEAFSIZE, R, E, L).
-
-to_orddict_3(0, _R, _D, L, _E, _S) -> %% when is_integer(R) ->
- L;
-to_orddict_3(N, R, D, L, E, S) ->
- to_orddict_3(N-1, R - S, D,
- to_orddict_2(element(N, E), R, D, L),
- E, S).
-
--spec push_pairs(non_neg_integer(), array_indx(), term(), indx_pairs()) ->
- indx_pairs().
-
-push_pairs(0, _I, _E, L) ->
- L;
-push_pairs(N, I, E, L) ->
- push_pairs(N-1, I-1, E, [{I, E} | L]).
-
--spec push_tuple_pairs(non_neg_integer(), array_indx(), term(), indx_pairs()) ->
- indx_pairs().
-
-push_tuple_pairs(0, _I, _T, L) ->
- L;
-push_tuple_pairs(N, I, T, L) ->
- push_tuple_pairs(N-1, I-1, T, [{I, element(N, T)} | L]).
-
-
--ifdef(EUNIT).
-to_orddict_test_() ->
- N0 = ?LEAFSIZE,
- [?_assert([] =:= to_orddict(new())),
- ?_assert([{0,undefined}] =:= to_orddict(new(1))),
- ?_assert([{0,undefined},{1,undefined}] =:= to_orddict(new(2))),
- ?_assert([{N,0}||N<-lists:seq(0,N0-1)]
- =:= to_orddict(new(N0,{default,0}))),
- ?_assert([{N,1}||N<-lists:seq(0,N0)]
- =:= to_orddict(new(N0+1,{default,1}))),
- ?_assert([{N,2}||N<-lists:seq(0,N0+1)]
- =:= to_orddict(new(N0+2,{default,2}))),
- ?_assert([{N,6}||N<-lists:seq(0,665)]
- =:= to_orddict(new(666,{default,6}))),
- ?_assert([{0,1},{1,2},{2,3}] =:=
- to_orddict(set(2,3,set(1,2,set(0,1,new()))))),
- ?_assert([{0,3},{1,2},{2,1}] =:=
- to_orddict(set(0,3,set(1,2,set(2,1,new()))))),
- ?_assert([{0,1}|[{N,0}||N<-lists:seq(1,N0-2)]++[{N0-1,1}]]
- =:= to_orddict(set(N0-1,1,set(0,1,new({default,0}))))),
- ?_assert([{0,1}|[{N,0}||N<-lists:seq(1,N0-1)]++[{N0,1}]]
- =:= to_orddict(set(N0,1,set(0,1,new({default,0}))))),
- ?_assert([{0,1}|[{N,0}||N<-lists:seq(1,N0)]++[{N0+1,1}]]
- =:= to_orddict(set(N0+1,1,set(0,1,new({default,0}))))),
- ?_assert([{0,0} | [{N,undefined}||N<-lists:seq(1,N0*2)]] ++
- [{N0*2+1,1} | [{N,undefined}||N<-lists:seq(N0*2+2,N0*10)]] ++
- [{N0*10+1,2}] =:=
- to_orddict(set(N0*10+1,2,set(N0*2+1,1,set(0,0,new()))))),
- ?_assertError(badarg, to_orddict(no_array))
- ].
--endif.
-
-
-%% @spec (array()) -> [{Index::integer(), Value::term()}]
-%% @doc Convert the array to an ordered list of pairs `{Index, Value}',
-%% skipping default-valued entries.
-%%
-%% @see to_orddict/1
-
--spec sparse_to_orddict(array()) -> indx_pairs().
-
-sparse_to_orddict(#array{size = 0}) ->
- [];
-sparse_to_orddict(#array{size = N, elements = E, default = D}) ->
- I = N - 1,
- sparse_to_orddict_1(E, I, D, I);
-sparse_to_orddict(_) ->
- error(badarg).
-
-%% see to_orddict/1 for details
-
-sparse_to_orddict_1(E=?NODEPATTERN(S), R, D, I) ->
- N = I div S,
- I1 = I rem S,
- sparse_to_orddict_3(N, R - I1 - 1, D,
- sparse_to_orddict_1(element(N+1, E), R, D, I1),
- E, S);
-sparse_to_orddict_1(E, _R, _D, _I) when is_integer(E) ->
- [];
-sparse_to_orddict_1(E, R, D, I) ->
- sparse_push_tuple_pairs(I+1, R, D, E, []).
-
-sparse_to_orddict_2(E=?NODEPATTERN(S), R, D, L) ->
- sparse_to_orddict_3(?NODESIZE, R, D, L, E, S);
-sparse_to_orddict_2(E, _R, _D, L) when is_integer(E) ->
- L;
-sparse_to_orddict_2(E, R, D, L) ->
- sparse_push_tuple_pairs(?LEAFSIZE, R, D, E, L).
-
-sparse_to_orddict_3(0, _R, _D, L, _E, _S) -> % when is_integer(R) ->
- L;
-sparse_to_orddict_3(N, R, D, L, E, S) ->
- sparse_to_orddict_3(N-1, R - S, D,
- sparse_to_orddict_2(element(N, E), R, D, L),
- E, S).
-
--spec sparse_push_tuple_pairs(non_neg_integer(), array_indx(),
- _, _, indx_pairs()) -> indx_pairs().
-
-sparse_push_tuple_pairs(0, _I, _D, _T, L) ->
- L;
-sparse_push_tuple_pairs(N, I, D, T, L) ->
- case element(N, T) of
- D -> sparse_push_tuple_pairs(N-1, I-1, D, T, L);
- E -> sparse_push_tuple_pairs(N-1, I-1, D, T, [{I, E} | L])
- end.
-
-
--ifdef(EUNIT).
-sparse_to_orddict_test_() ->
- N0 = ?LEAFSIZE,
- [?_assert([] =:= sparse_to_orddict(new())),
- ?_assert([] =:= sparse_to_orddict(new(1))),
- ?_assert([] =:= sparse_to_orddict(new(1,{default,0}))),
- ?_assert([] =:= sparse_to_orddict(new(2))),
- ?_assert([] =:= sparse_to_orddict(new(2,{default,0}))),
- ?_assert([] =:= sparse_to_orddict(new(N0,{default,0}))),
- ?_assert([] =:= sparse_to_orddict(new(N0+1,{default,1}))),
- ?_assert([] =:= sparse_to_orddict(new(N0+2,{default,2}))),
- ?_assert([] =:= sparse_to_orddict(new(666,{default,6}))),
- ?_assert([{0,1},{1,2},{2,3}] =:=
- sparse_to_orddict(set(2,3,set(1,2,set(0,1,new()))))),
- ?_assert([{0,3},{1,2},{2,1}] =:=
- sparse_to_orddict(set(0,3,set(1,2,set(2,1,new()))))),
- ?_assert([{0,1},{N0-1,1}] =:=
- sparse_to_orddict(set(N0-1,1,set(0,1,new({default,0}))))),
- ?_assert([{0,1},{N0,1}] =:=
- sparse_to_orddict(set(N0,1,set(0,1,new({default,0}))))),
- ?_assert([{0,1},{N0+1,1}] =:=
- sparse_to_orddict(set(N0+1,1,set(0,1,new({default,0}))))),
- ?_assert([{0,0},{N0*2+1,1},{N0*10+1,2}] =:=
- sparse_to_orddict(set(N0*10+1,2,set(N0*2+1,1,set(0,0,new()))))),
- ?_assertError(badarg, sparse_to_orddict(no_array))
- ].
--endif.
-
-
-%% @spec (list()) -> array()
-%% @equiv from_orddict(Orddict, undefined)
-
--spec from_orddict(indx_pairs()) -> array().
-
-from_orddict(Orddict) ->
- from_orddict(Orddict, undefined).
-
-%% @spec (list(), term()) -> array()
-%% @doc Convert an ordered list of pairs `{Index, Value}' to a
-%% corresponding extendible array. `Default' is used as the value for
-%% uninitialized entries of the array. If `List' is not a proper,
-%% ordered list of pairs whose first elements are nonnegative
-%% integers, the call fails with reason `badarg'.
-%%
-%% @see new/2
-%% @see to_orddict/1
-
--spec from_orddict(indx_pairs(), term()) -> array().
-
-from_orddict([], Default) ->
- new({default,Default});
-from_orddict(List, Default) when is_list(List) ->
- {E, N, M} = from_orddict_0(List, 0, ?LEAFSIZE, Default, []),
- #array{size = N, max = M, default = Default, elements = E};
-from_orddict(_, _) ->
- error(badarg).
-
-%% 2 pass implementation, first pass builds the needed leaf nodes
-%% and adds hole sizes.
-%% (inserts default elements for missing list entries in the leafs
-%% and pads the last tuple if necessary).
-%% Second pass builds the tree from the leafs and the holes.
-%%
-%% Doesn't build/expand unnecessary leaf nodes which costs memory
-%% and time for sparse arrays.
-
-from_orddict_0([], N, _Max, _D, Es) ->
- %% Finished, build the resulting tree
- case Es of
- [E] ->
- {E, N, ?LEAFSIZE};
- _ ->
- collect_leafs(N, Es, ?LEAFSIZE)
- end;
-
-from_orddict_0(Xs=[{Ix1, _}|_], Ix, Max0, D, Es0)
- when Ix1 > Max0, is_integer(Ix1) ->
- %% We have a hole larger than a leaf
- Step = ((Ix1-Ix) div ?LEAFSIZE) * ?LEAFSIZE,
- Next = Ix+Step,
- from_orddict_0(Xs, Next, Next+?LEAFSIZE, D, [Step|Es0]);
-from_orddict_0(Xs0=[{_, _}|_], Ix0, Max, D, Es) ->
- %% Fill a leaf
- {Xs,E,Ix} = from_orddict_1(Ix0, Max, Xs0, Ix0, D, []),
- from_orddict_0(Xs, Ix, Ix+?LEAFSIZE, D, [E|Es]);
-from_orddict_0(Xs, _, _, _,_) ->
- error({badarg, Xs}).
-
-from_orddict_1(Ix, Ix, Xs, N, _D, As) ->
- %% Leaf is full
- E = list_to_tuple(lists:reverse(As)),
- {Xs, E, N};
-from_orddict_1(Ix, Max, Xs, N0, D, As) ->
- case Xs of
- [{Ix, Val} | Xs1] ->
- N = Ix+1,
- from_orddict_1(N, Max, Xs1, N, D, [Val | As]);
- [{Ix1, _} | _] when is_integer(Ix1), Ix1 > Ix ->
- N = Ix+1,
- from_orddict_1(N, Max, Xs, N, D, [D | As]);
- [_ | _] ->
- error({badarg, Xs});
- _ ->
- from_orddict_1(Ix+1, Max, Xs, N0, D, [D | As])
- end.
-
-%% Es is reversed i.e. starting from the largest leafs
-collect_leafs(N, Es, S) ->
- I = (N-1) div S + 1,
- Pad = ((?NODESIZE - (I rem ?NODESIZE)) rem ?NODESIZE) * S,
- case Pad of
- 0 ->
- collect_leafs(?NODESIZE, Es, S, N, [S], []);
- _ -> %% Pad the end
- collect_leafs(?NODESIZE, [Pad|Es], S, N, [S], [])
- end.
-
-collect_leafs(0, Xs, S, N, As, Es) ->
- E = list_to_tuple(As),
- case Xs of
- [] ->
- case Es of
- [] ->
- {E, N, ?extend(S)};
- _ ->
- collect_leafs(N, lists:reverse([E | Es]),
- ?extend(S))
- end;
- _ ->
- collect_leafs(?NODESIZE, Xs, S, N, [S], [E | Es])
- end;
-collect_leafs(I, [X | Xs], S, N, As0, Es0)
- when is_integer(X) ->
- %% A hole, pad accordingly.
- Step0 = (X div S),
- if
- Step0 < I ->
- As = push(Step0, S, As0),
- collect_leafs(I-Step0, Xs, S, N, As, Es0);
- I =:= ?NODESIZE ->
- Step = Step0 rem ?NODESIZE,
- As = push(Step, S, As0),
- collect_leafs(I-Step, Xs, S, N, As, [X|Es0]);
- I =:= Step0 ->
- As = push(I, S, As0),
- collect_leafs(0, Xs, S, N, As, Es0);
- true ->
- As = push(I, S, As0),
- Step = Step0 - I,
- collect_leafs(0, [Step*S|Xs], S, N, As, Es0)
- end;
-collect_leafs(I, [X | Xs], S, N, As, Es) ->
- collect_leafs(I-1, Xs, S, N, [X | As], Es);
-collect_leafs(?NODESIZE, [], S, N, [_], Es) ->
- collect_leafs(N, lists:reverse(Es), ?extend(S)).
-
--ifdef(EUNIT).
-from_orddict_test_() ->
- N0 = ?LEAFSIZE,
- N1 = ?NODESIZE*N0,
- N2 = ?NODESIZE*N1,
- N3 = ?NODESIZE*N2,
- N4 = ?NODESIZE*N3,
- [?_assert(array:size(from_orddict([])) =:= 0),
- ?_assert(array:is_fix(from_orddict([])) =:= false),
- ?_assert(array:size(from_orddict([{0,undefined}])) =:= 1),
- ?_assert(array:is_fix(from_orddict([{0,undefined}])) =:= false),
- ?_assert(array:size(from_orddict([{N0-1,undefined}])) =:= N0),
- ?_assert(array:size(from_orddict([{N,0}||N<-lists:seq(0,N1-1)]))
- =:= N1),
- ?_assertError({badarg,_}, from_orddict([foo])),
- ?_assertError({badarg,_}, from_orddict([{200,foo},{1,bar}])),
- ?_assertError({badarg,_}, from_orddict([{N,0}||N<-lists:seq(0,N0-1)] ++ not_a_list)),
- ?_assertError(badarg, from_orddict(no_array)),
-
-
- ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N0-1)],
- L =:= to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N0)],
- L =:= to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N2-1)],
- L =:= to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N2)],
- L =:= to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N3-1)],
- L =:= to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{N,0}||N<-lists:seq(0,N4-1)],
- L =:= to_orddict(from_orddict(L)))),
-
- %% Hole in the begining
- ?_assert(?LET(L, [{0,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{N0,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{N1,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{N3,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{N4,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{N0-1,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{N1-1,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{N3-1,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{N4-1,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
-
- %% Hole in middle
-
- ?_assert(?LET(L, [{0,0},{N0,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{0,0},{N1,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{0,0},{N3,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{0,0},{N4,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{0,0},{N0-1,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{0,0},{N1-1,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{0,0},{N3-1,0}],
- L =:= sparse_to_orddict(from_orddict(L)))),
- ?_assert(?LET(L, [{0,0},{N4-1,0}],
- L =:= sparse_to_orddict(from_orddict(L))))
-
- ].
--endif.
-
-
-%% @spec (Function, array()) -> array()
-%% Function = (Index::integer(), Value::term()) -> term()
-%% @doc Map the given function onto each element of the array. The
-%% elements are visited in order from the lowest index to the highest.
-%% If `Function' is not a function, the call fails with reason `badarg'.
-%%
-%% @see foldl/3
-%% @see foldr/3
-%% @see sparse_map/2
-
--spec map(fun((array_indx(), _) -> _), array()) -> array().
-
-map(Function, Array=#array{size = N, elements = E, default = D})
- when is_function(Function, 2) ->
- if N > 0 ->
- A = Array#array{elements = []}, % kill reference, for GC
- A#array{elements = map_1(N-1, E, 0, Function, D)};
- true ->
- Array
- end;
-map(_, _) ->
- error(badarg).
-
-%% It might be simpler to traverse the array right-to-left, as done e.g.
-%% in the to_orddict/1 function, but it is better to guarantee
-%% left-to-right application over the elements - that is more likely to
-%% be a generally useful property.
-
-map_1(N, E=?NODEPATTERN(S), Ix, F, D) ->
- list_to_tuple(lists:reverse([S | map_2(1, E, Ix, F, D, [],
- N div S + 1, N rem S, S)]));
-map_1(N, E, Ix, F, D) when is_integer(E) ->
- map_1(N, unfold(E, D), Ix, F, D);
-map_1(N, E, Ix, F, D) ->
- list_to_tuple(lists:reverse(map_3(1, E, Ix, F, D, N+1, []))).
-
-map_2(I, E, Ix, F, D, L, I, R, _S) ->
- map_2_1(I+1, E, [map_1(R, element(I, E), Ix, F, D) | L]);
-map_2(I, E, Ix, F, D, L, N, R, S) ->
- map_2(I+1, E, Ix + S, F, D,
- [map_1(S-1, element(I, E), Ix, F, D) | L],
- N, R, S).
-
-map_2_1(I, E, L) when I =< ?NODESIZE ->
- map_2_1(I+1, E, [element(I, E) | L]);
-map_2_1(_I, _E, L) ->
- L.
-
--spec map_3(pos_integer(), _, array_indx(),
- fun((array_indx(),_) -> _), _, non_neg_integer(), [X]) -> [X].
-
-map_3(I, E, Ix, F, D, N, L) when I =< N ->
- map_3(I+1, E, Ix+1, F, D, N, [F(Ix, element(I, E)) | L]);
-map_3(I, E, Ix, F, D, N, L) when I =< ?LEAFSIZE ->
- map_3(I+1, E, Ix+1, F, D, N, [D | L]);
-map_3(_I, _E, _Ix, _F, _D, _N, L) ->
- L.
-
-
-unfold(S, _D) when S > ?LEAFSIZE ->
- ?NEW_NODE(?reduce(S));
-unfold(_S, D) ->
- ?NEW_LEAF(D).
-
-
--ifdef(EUNIT).
-map_test_() ->
- N0 = ?LEAFSIZE,
- Id = fun (_,X) -> X end,
- Plus = fun(N) -> fun (_,X) -> X+N end end,
- Default = fun(_K,undefined) -> no_value;
- (K,V) -> K+V
- end,
- [?_assertError(badarg, map([], new())),
- ?_assertError(badarg, map([], new(10))),
- ?_assert(to_list(map(Id, new())) =:= []),
- ?_assert(to_list(map(Id, new(1))) =:= [undefined]),
- ?_assert(to_list(map(Id, new(5,{default,0}))) =:= [0,0,0,0,0]),
- ?_assert(to_list(map(Id, from_list([1,2,3,4]))) =:= [1,2,3,4]),
- ?_assert(to_list(map(Plus(1), from_list([0,1,2,3]))) =:= [1,2,3,4]),
- ?_assert(to_list(map(Plus(-1), from_list(lists:seq(1,11))))
- =:= lists:seq(0,10)),
- ?_assert(to_list(map(Plus(11), from_list(lists:seq(0,99999))))
- =:= lists:seq(11,100010)),
- ?_assert([{0,0},{N0*2+1,N0*2+1+1},{N0*100+1,N0*100+1+2}] =:=
- sparse_to_orddict((map(Default,
- set(N0*100+1,2,
- set(N0*2+1,1,
- set(0,0,new())))))#array{default = no_value}))
- ].
--endif.
-
-
-%% @spec (Function, array()) -> array()
-%% Function = (Index::integer(), Value::term()) -> term()
-%% @doc Map the given function onto each element of the array, skipping
-%% default-valued entries. The elements are visited in order from the
-%% lowest index to the highest. If `Function' is not a function, the
-%% call fails with reason `badarg'.
-%%
-%% @see map/2
-
--spec sparse_map(fun((array_indx(), _) -> _), array()) -> array().
-
-sparse_map(Function, Array=#array{size = N, elements = E, default = D})
- when is_function(Function, 2) ->
- if N > 0 ->
- A = Array#array{elements = []}, % kill reference, for GC
- A#array{elements = sparse_map_1(N-1, E, 0, Function, D)};
- true ->
- Array
- end;
-sparse_map(_, _) ->
- error(badarg).
-
-%% see map/2 for details
-%% TODO: we can probably optimize away the use of div/rem here
-
-sparse_map_1(N, E=?NODEPATTERN(S), Ix, F, D) ->
- list_to_tuple(lists:reverse([S | sparse_map_2(1, E, Ix, F, D, [],
- N div S + 1,
- N rem S, S)]));
-sparse_map_1(_N, E, _Ix, _F, _D) when is_integer(E) ->
- E;
-sparse_map_1(_N, E, Ix, F, D) ->
- list_to_tuple(lists:reverse(sparse_map_3(1, E, Ix, F, D, []))).
-
-sparse_map_2(I, E, Ix, F, D, L, I, R, _S) ->
- sparse_map_2_1(I+1, E,
- [sparse_map_1(R, element(I, E), Ix, F, D) | L]);
-sparse_map_2(I, E, Ix, F, D, L, N, R, S) ->
- sparse_map_2(I+1, E, Ix + S, F, D,
- [sparse_map_1(S-1, element(I, E), Ix, F, D) | L],
- N, R, S).
-
-sparse_map_2_1(I, E, L) when I =< ?NODESIZE ->
- sparse_map_2_1(I+1, E, [element(I, E) | L]);
-sparse_map_2_1(_I, _E, L) ->
- L.
-
--spec sparse_map_3(pos_integer(), _, array_indx(),
- fun((array_indx(),_) -> _), _, [X]) -> [X].
-
-sparse_map_3(I, T, Ix, F, D, L) when I =< ?LEAFSIZE ->
- case element(I, T) of
- D -> sparse_map_3(I+1, T, Ix+1, F, D, [D | L]);
- E -> sparse_map_3(I+1, T, Ix+1, F, D, [F(Ix, E) | L])
- end;
-sparse_map_3(_I, _E, _Ix, _F, _D, L) ->
- L.
-
-
--ifdef(EUNIT).
-sparse_map_test_() ->
- N0 = ?LEAFSIZE,
- Id = fun (_,X) -> X end,
- Plus = fun(N) -> fun (_,X) -> X+N end end,
- KeyPlus = fun (K,X) -> K+X end,
- [?_assertError(badarg, sparse_map([], new())),
- ?_assertError(badarg, sparse_map([], new(10))),
- ?_assert(to_list(sparse_map(Id, new())) =:= []),
- ?_assert(to_list(sparse_map(Id, new(1))) =:= [undefined]),
- ?_assert(to_list(sparse_map(Id, new(5,{default,0}))) =:= [0,0,0,0,0]),
- ?_assert(to_list(sparse_map(Id, from_list([1,2,3,4]))) =:= [1,2,3,4]),
- ?_assert(to_list(sparse_map(Plus(1), from_list([0,1,2,3])))
- =:= [1,2,3,4]),
- ?_assert(to_list(sparse_map(Plus(-1), from_list(lists:seq(1,11))))
- =:= lists:seq(0,10)),
- ?_assert(to_list(sparse_map(Plus(11), from_list(lists:seq(0,99999))))
- =:= lists:seq(11,100010)),
- ?_assert(to_list(sparse_map(Plus(1), set(1,1,new({default,0}))))
- =:= [0,2]),
- ?_assert(to_list(sparse_map(Plus(1),
- set(3,4,set(0,1,new({default,0})))))
- =:= [2,0,0,5]),
- ?_assert(to_list(sparse_map(Plus(1),
- set(9,9,set(1,1,new({default,0})))))
- =:= [