Arrays and Tables in Endymion

Introduction

The symbolic reader reads a[b] and a[b,c] as array(a,b) or array(a,b,c). This will be interpreted by the compiler as an array reference to object a with indices b and c. In the case of one index, the function array1 will be used instead of array, it is a bit optimised. If you say a[b]:=x or a[b,c]:=x this is converted to arrstore(a,x,b) or arrstore(a,x,b,c). This stores expression x in the object a at positions b or b and c. The function arrstore1 will be used in the case of a single index. Finally, let(a[b]:=c,body) is interpreted as let(x=a[b], a[b]:=c, protect(body,a[b]:=x))

Every Lisp object has an array method, see below. We start with describing symbolic arrays.

The index notation in the example that follows has nothing to do with arrays.

(1) a index b;

(1)                                   a
                                       b

(2) indexed(a,b,c);

(2)                                a
                                     b, c

Symbolic arrays

A symbolic array is an object that can be used to store other objects. It has eight fields: NAME, ARRAY, ETYPE, ITYPE, NBDIM, AUX, FUN, TRACE. Here ARRAY is the vector that holds the data, NAME is the name of the array (that can be accessed via array_name). In some case a generating function is associated to the array in FUN, and if the array is traced, the TRACE field is non-empty.

The ETYPE field explains the layout of the array. As example (1) below shows, it can be lisp, hash, extensible, or enumerated, this will be explained later. The number of indices is in NBDIM and the field AUX contains for each index its type. The field ITYPE contains the type of what can be put in the array. Example (2) gives the list of all known index or value types. Example (3) shows that you can give a function instead of a type. Examples (4 (6) and (7) show what happens in case of illegal value type. Examples (8) and (9) show a case whith legal types.

(1) makearray("array", mytype, all,all);

makearray : type should be lisp, hash, extensible or enumerated : mytype
(2) makearray("array", lisp, foo,all);

makearray : Bad type, (you may consider one of [rat, rational_number, rat>=, 

nonnegrat, non_negative_rational_number, rat>, posrat, 

positive_rational_number, rat<=, nonposrat, non_positive_rational_number, 

rat<, negrat, negative_rational_number, num<, negnum, negative_number, 

num<=, nonposnum, non_positive_number, int<, negint, negative_integer, 

int<=, nonposint, non_positive_integer, bool, boolean, num>, posnum, 

positive_number, num>=, nonnegnum, non_negative_number, int>=, nonnegint, 

non_negative_integer, int>, posint, positive_integer, number, string, 

everything, any, all, int, integer, any function with one argument]) : foo

(3) defarray(f1, hash, all, lambda(u, if 0<u and u < 4 then true));

(3)                             <ARRAY: f1>

(4) f1[0];
<ARRAY: f1> : function <FUNCTION: u> should return true : 0

(5) defarray(f2, hash, 10,rat);

(5)                             <ARRAY: f2>

(6) f2[1/2]:=11;
<ARRAY: f2> : argument a does not satisfy 0 <= a <= 9 : 11

(7) f2[1.5]:=9;
<ARRAY: f2> : should be of type rational_number : 1.5;

(8) f2[1/2]:=9;

(8)                                  9

(9) f1[2]:=4;

(9)                                  4

The function makearray(name,TE,TI, i1,...,in)

This function creates a symbolic array. The first argument is the name, the second is the array type, then comes the value type and the type of indices. If TE is hash, then the ARRAY field is a hash table, whose size changes dynamically; if is is lisp, an n-dimensional Lisp array will be used. This uses a fixed amount of memory, namely the product of the ik (which must be positive numbers). Finally if TE is enumerated then a single index is used, whose value is one of i1,..., in; an extensible array is like an enumerated array, but the list of valid indices can be adjusted.

We start with four examples. Note that all variables are evaluated; we assume that the lisp symbol evaluates to itself. You should use the character string "lisp" instead. The same remark is true for a in the third example. If the symbol a evaluates to itsef, then z[a] and z["a"] are identical. This applies to the extensible w but not to a normal array: y[a,b] is not the same as y["a","b"], because symbols are not automatically converted to strings.

(1) x:= makearray(log(w^2), lisp, all,10);

(1)                           <ARRAY: log(w**2)>

(2) y:= makearray("array", hash, all,all,all);

(2)                             <ARRAY: array>

(3) z:=makearray("z","enumerated",all,a,b,c);

(3)                               <ARRAY: z>

(4) w:=makearray("w","extensible",int,a1,a2,a3);

(4)                               <ARRAY: w>

The macro defarray(name,TE,TI, i1,...,in)

The expansion of the macro is tname:=makearray(name,TE,TI, i1,...,in): this puts the array in a variable. An error is signaled in case the first argument is not a variable. Note that in the case of makearray any expression is a valid name; this will be converted to character string by the printer (and truncated if too long). The name can be obtained by the array_name function.

(5) defarray(foo,lisp,int,3);

(5)                              <ARRAY: foo>

(6) array_name(foo);

(6)                                  foo

(7) defarray(cons, lisp, all,10);
Error : not a variable : cons
(8)  array_name(17);
array_name : not an array : 17
(9)  defarray(x,lisp,all,0,1);
makearray : Bad size for declared array : 0

Basic functions

Once an array is defined, you can use it to store and retrieve data. Initially, the array is empty, trying to access it provokes an error. The case of an enumerated array is special. Let Z be that array field of z. You can say z[a], z[b] z[c] and nothing else. This can be optimised by the compiler as #vref(Z,0), #vref(Z,1) and #vref(Z,2). As a result, z[a] is always defined (initially false), any Lisp object can be stored in the table, and tracing the array has no effect. This optimisation is not done for extensible arrays: they can be traced, the initial value is undefined, and values are checked, see lines (15) and (16). Consider line (17): when we store something in the array at position a4, the array is extended, and a4 is added to the list of valid indices. As line (19) shows, valid indices must be strings (or symbols that can be converted to a string). When you say w[x], Endymion computes the integer n such that nth(n,L)=x, where L is the list of valid indices, then accessses to the n-th element of the vector. In the case of an enumerated array, this computation can be done by the compiler. In the case of an extensible array, you can use n directlty, if you know the value, see line (20).

(10)  x[1];

 <ARRAY: log(w**2)> : nothing in array at position [1] : <ARRAY: log(w**2)>
(11) x[1]:=10;

(11)                                  10

(12) z[1]:=10;

<ARRAY: z> : Bad index (should be in [a, b, c]) : 1

(13) z[a];

(13)                                false

(14) x[2]:=11;

(14)                                  11

(15) w[a2];
<ARRAY: w> : nothing in array at position a2 : <ARRAY: w>

(16) w[a1]:=0.5;
<ARRAY: w> : should be of type integer : 0.5

(17) w[a4]:=10;

(17)                                   10

(18) w[0.5];
<ARRAY: w> : Bad index (should be in [a1, a2, a3, a4]) : 0.5
(19) w[0.5]:=1;
<ARRAY: w> : argument out of bounds : 0.5

(20) w[3];

(20)                                   10

The functions array_to_list(T), array_to_seq(T)

It is possible to convert the content of an array into a list and vice versa. The array_to_list(f) returns a list a list of items, each item is a list, formed of a value followed by the indices. The array_to_seq function returns the same object, presented as a sequence (exemple 27). In general, the sequence is implicit, as in example 26.

(20) for k in 0...9 do x[k]:=k^3;

(20)                                 done


(21) array_to_list(x);

(21)  [[729, 9], [512, 8], [343, 7], [216, 6], [125, 5], [64, 4], [27, 3], 

[8, 2], [1, 1], [0, 0]]

(22) for k in 1...9 do y[k,k+1]:=[k];

(22)                                 done

(23) array_to_list(y);

(23)  [[[2], 2, 3], [[6], 6, 7], [[3], 3, 4], [[7], 7, 8], [[4], 4, 5], 

[[8], 8, 9], [[1], 1, 2], [[5], 5, 6], [[9], 9, 10]]


(24) z[a]:=z[b]:=z[c]:=0;

(24)                                  0

(25) array_to_list(z);

(25)                       [[0, c], [0, b], [0, a]]


(26) for w in x do print(w);
[11, 2]
[10, 1]

(26)                                 done

(27) array_to_seq(z);

(27)                    <UninitializedSequence: array>

(28) array_to_list(w);

(28)                              [[11, a4]]

The function array_info(T)

The array_info function returns some information about the array. The first element of the resulting list is the type (0 for a Lisp array, 1 for a hash table, 2 for an enumeration, 3 fr ann extensible array). In the case of an enumeration or extensible array, there is the list of valid indices, otherwise the number of elements. In the case of a Lisp table, this is followed by the list of dimensions, otherwise by the length of the buckets; the number of buckets is initially 8, its value N is so that the average length of a bucket is no more than N/4, and at least N/16.

(29) array_info(x);

(29)                             [0, 10, 10]

(30) array_info(y);

(30)                    [1, 9, 0, 3, 0, 2, 0, 2, 0, 2]

(31) array_info(z);

(31)                             [2, a, b, c]

(32) array_info(w);

(32)                         [3, a1, a2, a3, a4]

The functions killarray(x,sw) and killarray_element(a,b,c)

If you say killarray(x,sw) this removes a part of the data associated to the array x. If sw is 2, this destroys the generating function, if sw is 1, this destroys the data, and if sw is 0, this destroys both. You can try to remove an element from an array via a[b,c]:=#_undef_, but this fails because the RHS is undefined. However, the construction killarray_element(a,b,c) works.

(33) killarray(x,1);

(33)                          7lt;ARRAY: log(w**2)>


(34) array_info(x);

(34)                              [0, 0, 10]

(35) killarray(x,3);
() : argument out of bounds : 3

(36) killarray(3,x);
killarray : not an array : 3

(37) killarray_element(y,123);
#:feval:killarray_element : wrong number of arguments : 1 this should be 2

The function test_array_element(t,i1,...,in)

The function call test_array_element(a,b,c) returns true if a is asymbolicd array and a[b,c] is valid (in particular, if there is something in the array at the given position).

(38) test_array_element(x,1,2);

(38)                               false

(39) test_array_element(x,3);

(39)                               false

(40) test_array_element(y,5,6);

(40)                                true

(41) test_array_element(z,d);

(41)                               false

(42) test_array_element(z,a);

(42)                                true

(43) test_array_element(f1,"x");

(43)                                false

(44) test_array_element(w,a4);

(44)                                 true

(45) test_array_element(w,3);

(45)                                 true

(46) test_array_element(w,a5);

(46)                                false

The function seq_to_array(S,T)

Assume that T is an array with indices, S a sequence of list of size n+1. The function converts the list into an array via for L in S do apply(arrstore,cons(T,L)). Remember that xT[a,b]:=c is the same as arrstore(T,c,a,b). As line (49) shows this may extend the array.

(47) seq_to_array([[10,1],[20,2]],x);

(47)                          <ARRAY: log(w**2)>

(48) for a in x and b  do if b>6 then print(a);
[4, 3]
[20, 2]
[10, 1]
[1, 0]

(48)                                 done

(49) seq_to_array([[1,x1],[2,x3],[3,a1]],w);

(49)                              <ARRAY: w>

The function fillarray(T,f,S)

Assume that f is a function with one argument. The effect of fillarray is to execute T[i]:=f([i]) for i in S. More precisely, let B be the function B(a,f):=cons(f(a),a) (Endymion uses a function with a funny name instead of B). Then B([i,j],f) gives [f([i,j], i,j]. Let nS be apply_seq(B,S,f); this is the sequence with generic term B(i,f) for i in S. Then fillarray(T,f,S) is just seq_to_array(nS,T). In examples (50) and (52) below, the arrays have fixed size, and the argument S can be omitted, the default being the set of all valid indices. In example (59), we use the array notation u[0] in order to get the first element of the list, this is a symbol and u[0]["sname"] returns its name as a character string; finally u[0]["sname"][1] is the ASCII code of the second character of the string.

(50) fillarray(x,lambda(u, car(u)+1));

(50)                           <ARRAY: log(w**2)>

(51) array_to_list(x);

(51)  [[10, 9], [9, 8], [8, 7], [7, 6], [6, 5], [5, 4], [4, 3], [3, 2], 

[2, 1], [1, 0]]

(52) fillarray(z,lambda(u,car(u)))?;


(53) S:=cartesian_product_seq(0...4,[u,v,w]);


(54)  <UninitializedSequence: CP, <vector: <UninitializedSequence: interval, 

0 ... 4>, <UninitializedSequence: list, [u, v, w]>>>

(55) fillarray(y,lambda(u, cons(car(u)+1, cdr(u))), S);

(55)                            <ARRAY: array>

(56) array_to_list(y);

(56)  [[[5, u], 4, u], [[4, v], 3, v], [[3, w], 2, w], [[2, u], 1, u], 

[[1, v], 0, v], [[5, w], 4, w], [[4, u], 3, u], [[3, v], 2, v], 

[[2, w], 1, w], [[1, u], 0, u], [[5, v], 4, v], [[4, w], 3, w], 

[[3, u], 2, u], [[2, v], 1, v], [[1, w], 0, w]]

(57) for a in x do print(a);
[10, 9]
[9, 8]
[8, 7]
[7, 6]
[6, 5]
[5, 4]
[4, 3]
[3, 2]
[2, 1]
[1, 0]

(58) array_to_list(w);

(58)                [[2, x3], [1, x1], [11, a4], [3, a1]]

(59) fillarray(w, lambda(u, u[0]["sname"][1]));

(59)                              <ARRAY: w>

(60) array_to_list(w);


(60)     [[51, x3], [49, x1], [52, a4], [51, a3], [50, a2], [49, a1]]

The function copyarray(x,y)

If you copyarray(x,y) this has as effect to copy the array x into y. In the case where x is an array, it is cleared before the copy, and the function and trace of x will be copied also. In any case, all elements for the first array are copied into the second: if X is array_to_seq(x) then seq_to_array(X,y) is executed.

(1) x:=makevector(10,nil);


(1)       <vector: nil, nil, nil, nil, nil, nil, nil, nil, nil, nil>

(2) defarray(y,hash,all,7);

(2)                               <ARRAY: y>

(3) {y[0]:=1,y[2]:=3,y[3]:=4,y[5]:=6}?;

(4) defarray(z,hash,all,7);

(4)                               <ARRAY: z>

(5) for i in 0...6 do z[i]:=false?;

(6) copyarray(y,z);


(6)                               <ARRAY: z>

(7) copyarray(y,x);


(7)          <vector: 1, nil, 3, 4, nil, 6, nil, nil, nil, nil>

(8) array_to_list(z);


(8)                   [[4, 3], [1, 0], [6, 5], [3, 2]]

The macro define_array_function(T,x,b)

The macro define_array_function(T,x,B) defines a function F, with one argument x, that evaluates to B, stores this in T at position x, and returns this value. The macro calls define-array-function(T,F). This sets the FUN field of the array T to F. If T[x] does not exist, but there is a function in the array, then F(x) is called. In the example that follows, the array behaves like the Fibonacci function. The fact that F stores the result in the table makes it very fast. This feature cannot be used for extensible or enumerated arrays.

(2) defarray(Fib,hash,all,all);

(2)                              <ARRAY: Fib>

(3) Fib[1]:=1;

(3)                                   1

(4) Fib[2]:=1;

(4)                                   1

(5) define_array_function(Fib,[n],Fib[n-1]+Fib[n-2]);

(5)                              <ARRAY: Fib>

(6) Fib[10];

(6)                                   55

Tracing an array

If you trace the table, you can see how the recursion works. You could also try to put Fib[n-2]+Fib[n-1] in the body and see what happens.

(level 0) Fib   (computing) --> 
             10
(level 1) Fib  (computing) --> 
             9
(level 2) Fib  (computing) --> 
             8
(level 3) Fib  (computing) --> 
             7
(level 4) Fib  (computing) --> 
             6
(level 5) Fib  (computing) --> 
             5
(level 6) Fib  (computing) --> 
             4
(level 7) Fib  (computing) --> 
             3
(level 8) Fib  --> 1
             2
(level 8) Fib  --> 1
             1
(level 8) Fib  <-- 2
             3
(level 8) Fib  --> 2
             3
(level 7) Fib  --> 1
             2
(level 7) Fib  <-- 3
             4
(level 7) Fib  --> 3
             4
(level 6) Fib  --> 2
             3
(level 6) Fib  <-- 5
             5
(level 6) Fib  --> 5
             5
(level 5) Fib  --> 3
             4
(level 5) Fib  <-- 8
             6
(level 5) Fib  --> 8
             6
(level 4) Fib  --> 5
             5
(level 4) Fib  <-- 13
             7
(level 4) Fib  --> 13
             7
(level 3) Fib  --> 8
             6
(level 3) Fib  <-- 21
             8
(level 3) Fib  --> 21
             8
(level 2) Fib  --> 13
             7
(level 2) Fib  <-- 34
             9
(level 2) Fib  --> 34
             9
(level 1) Fib  --> 21
             8
(level 1) Fib   <-- 55
             10
(level 1) Fib   --> 55
             10

(6)                                   55

Pseudo arrays

If M is a matrix, then M[1,2] refers to line 2 column 3 of the matrix, if P is a polynomial, then P[0] is the constant term of the polynomials. You can say M[1,2]:=0 or P[0]:=1 for changing the value. There are other cases where the array notation is read-only. We shall explain for each type of object what can be done. In any case the notation x[] return the type of x as a Lisp symbol. In particular [][] is the type of an empty list.

Lists

If L is a list, n an integer then L[n] is the n-th element of the list. As the example below shows, there are more restrictions on symbolic lists than on Lisp conses. If n is the string car or cdr then L[n] is the CAR or CDR of the list. Otherwise an error is signaled.

(1) L:=[1,2,3];

(1)                               [1, 2, 3]

(2) l:=#list(1,2,3);

(2)                                1(2, 3)

(3) [l[],L[]];

(3)                             [cons, flist]

(4) [l["car"],L["car"],l["cdr"],L["cdr"]];


(4)                          [1, 1, 2(3), [2, 3]]

(5) [l[0],l[1],l[2], L[0], L[1], L[2]];

(5)                           [1, 2, 3, 1, 2, 3]

(6) [l[-1],l[10]];


(6)                               [1, false]

(7) L[-1];
nth : argument out of bounds : - 1
(8) L[3];
nth : list too short : [1, 2, 3]
(10) L["foo"];
eval : not an array : [1, 2, 3][foo]
(11) L["foo","bar"];
[1, 2, 3][foo, bar] : wrong number of arguments this should be 1 : 2

We show here how a list can be modified. The same errors as above can be signaled. You can put anothing in the CDR of a cons, but a symbolic list is required in the case of a symbolic list.

(1) x:=[1,2,3];

(1)                               [1, 2, 3]

(2) x["car"]:=2;

(2)                                   2

(3) x["cdr"]:=[4,5,6];

(3)                               [4, 5, 6]

(4) x[2]:=17;

(5)                                   17

(5) x;

(6)                             [2, 4, 17, 6]

Symbols

If F is a symbol, you can say F[v], where v is a character string, according to the following rules. If v is name, the name of the symbol is returned (this is a Lisp object). If v is sname, the name of the symbol is returned as a character string. If v is plist a copy of the property list is returned. If v is value, the value of the symbol is returned (or false if there is no value). If F is a Lisp symbol, you can access to packagecell via packagecell. If F is a symbolic symbol, you can access its arity. In the example that follows, the variable log is in fact the Lisp symbol #:feval:log, and its property list contains information for the compiler. On line 1, we consider a variable L, to which we associate value, which is the Lisp symbol log. We show its plist. On line (7) we ask for the value of the symbol log: this is undefined, and we see false. Lines (8) and (9) produce the same result: We convert first the character string into a symbolic variable, and ask for its value; we get the value of the associated Lisp symbol.

(1) L:=log["name"];

(1)                                 log

(2) log[];

(2)                                 symbol

(3) log["plist"];

(3)  info(#:VT:cste:#[(function 1 () () expression expression) #:feval:log])

(4) L["plist"];

(4)                 tex-special(#:graphic:#[202 \log ])

(5) log["packagecell"];

(5)                                 feval

(6)  %e["arity"];

(6)                                  1

(7) L["value"];

(7)                                 false

(8) concat("L")["value"];

(8)                                  log

(9) concat("L")["name"]["value"];

(9)                                  log

Splines

A spline object is a sequence of Ppolys, and a ppoly is a sequence of 6 numbers, two real numbers and 4 complex numbers. A splines can be entered as #PSp n xxx where n is an integer and xxx a sequence of n times 10 real numbers. If S is a splines then S[i] is a vector of size 6, containing theta1, theta2, c0, c1, c2, c3, for the i-th ppoly. You can say S[i]=V, where V is a list or vector of size 6, provided that you do no change theta; Endymion allows also a list or vector of size 4, containing only the values ci. You can say S[i,j] or S[i,j]:=v, where j is a number between 0 and 5, or a symbolic name (see above). Note that S[i,1]=S[i+1,0], so that if you change the value of S[i,1] then S[i+1,0] is also changed. In the same way changing S[i,0] changes S[i-1,1]. Moreover we must have S[i,0]<S[i,1], this gives an uupper bound and a lower bound for each theta (the first theta has bno lower bound, the last theta has no upper bound).

(1)  S:=#PSp 2 1.66 1.77 -1.3 1.4 -1.5 1.6 -1.7 1.8 -1.9 
(1)    2 1.77 1.99 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.1;


(1)  <spline: <ppoly: 1.66, 1.77, - 1.3 + 1.4 %i, - 1.5 + 1.6 %i, 

- 1.7 + 1.8 %i, - 1.9 + 2 %i>, <ppoly: 1.77, 1.99, 2.3 + 2.4 %i, 

2.5 + 2.6 %i, 2.7 + 2.8 %i, 2.9 + 2.1 %i>>

(2) S[1];

(2)  

 <vector: 1.77, 1.99, 2.3 + 2.4 %i, 2.5 + 2.6 %i, 2.7 + 2.8 %i, 2.9 + 2.1 %i>

(3)  { S[0]:=[1.66,1.77,3,4,5,6], S[0] };

(3)                   <vector: 1.66, 1.77, 3, 4, 5, 6>

(4) { S[0]:=[1.66,1.77,3,4,5,6], S[0] };


(4)                   <vector: 1.66, 1.77, 1, 2, 3, 4>

(5) S[1,2];

(5)                             2.3 + 2.4 %i

(6) S[1,c0];


(6)                             2.3 + 2.4 %i

(7) S[0,1]:=1.88;


(7)                                  1.88

(8) S[1,theta1];


(8)                                  1.88

(8) S[1,theta1]:=10;
Making a value for theta1
setcoef : argument out of bounds : 10

Valid XHTML 1.0 Strict back to home page © INRIA 2005, 2006 Last modified $Date: 2008/10/20 16:44:27 $