411 REFERENCE · Wikibooks
Maxima/Data structures
Reference material by Wikibooks contributors. This retains its source identity and is not 411 news reporting.
Maxima Aggregate Data Structures
Introduction
This section is intended to present some of the essentials of what are called Maxima aggregate data structures. By this it is meant the data types that can be used to refer to the values of expressions using a means of indexing of a single identifier (name) that refers to an aggregate (collection) of expressions as values.
Built-in aggregate data types include:
lists
declared arrays
hashed (undeclared) arrays
Lisp arrays
structures
Each of these has a means of indexing an identifier that names an instance of these data types.
Please note that it is the case that this means of indexing an identifier for an aggregate data type may be referred to as involving "subscripts" or "sub-scripted variables" or "keys" as used by various computer programming languages, as well as in the context of Maxima documentation and commentary. In this book terms such as "index" or "indices" or "indexed identifiers" are used instead.
Also all built-in Maxima functions (including packages) for these data types are referred to as "operators" and not "functions" as this latter term in general serves better to refer to Maxima function expressions defined by the user -- as in f(x) :=, g[s] := and define () as examples.
This section is not intended to be exhaustive. It is intended to provide familiarity with aggregate data types. What is included has been gleaned from various sources, and only a few attributions are provided. Therefore, there is no claim of originality implied.
Lists
Introduction to Lists
Lists are one of the basic buildings block for Maxima and Lisp. Maxima expressions are in general represented as Lisp lists.
For example, consider the Maxima expression:(%i1) e+3;
(%o1) e+3This expression has the form of a Lisp list:(%i2) :lisp #$[e+3]$
((MLIST SIMP) ((MPLUS SIMP) 3 $E))An expression in the form of a Lisp list can also be be displayed as a Maxima expression:(%i2) :lisp (displa '((MLIST SIMP) ((MPLUS SIMP) 3 $E)));
[e+3]
NILNote that the value of this expression using displa '() is NIL, and [e+3] is simply displayed in Maxima list object form. It is not necessary for the typical uses of Maxima to utilize this insight into the role Lisp lists have as one of the building blocks for Maxima, but it may be useful to have a familiarity with that role. Lists are often used as arguments to many operators, and the values of expressions are often in the form of lists.
Some Operators for Lists
Lists are created explicitly and list elements referenced using [ and ] to mark the beginning and end, respectively, of an instance of a list object, or to reference the expressions of an existing list object.(%i3) [2,3,4];
(%o3) [2,3,4]The expression [2,3,4] creates a Maxima list object. In this case the object itself is not accessible as it was not assigned an identifier unless referenced by the % , %i or %o label.(%i4) %;
(%o4) [2,3,4]
(%i5) %o4[2]; /* second expression of list object referenced by index */
(%o5) 3That [2,3,4] is a list object can be tested using the listp() operator:(%i6) listp ([2,3,4]);
(%o6) trueA list object can be assigned an identifier:(%i7) s : [%pi,%e,%phi];
(s) [%pi,%e,%phi]
(%i8) listp (s);
(%o8) true
The value of an element of a list can be accessed using the list identifier and brackets with a index value.(%i9) s[1];
(%o9) %pi
(%i10) s[2];
(%o10) %e
(%i11) s[3];
(%o11) %phi
Invalid index values returns an error:(%i12) s[4];
inpart: invalid index 4 of list or matrix.
-- an error. To debug this try: debugmode(true);
Consider the following relatively long list of integer atoms created explicitly and identified as List_1:(%i13) List_1 : [1,118,30,1,484,58,1,664,87,1,1004,115,1,1231,120,1,1372,
142,1,1582,145,2,118,33,2,484,69,2,664,111,2,1004,156,2,
1231,172,2,1372,203,2,1582,203,3,118,30,3,484,51,3,664,
75,3,1004,108,3,1231,115,3,1372,139,3,1582,140,4,118,32,
4,484,62,4,664,112,4,1004,167,4,1231,179,4,1372,209,4,
1582,214,5,118,30,5,484,49,5,664,81,5,1004,125,5,1231,
142,5,1372,174,5,1582,177];
(List_1) [1,118,30,1,484,58,1,664,87,1,1004,115,1,1231,120,1,1372,
142,1,1582,145,2,118,33,2,484,69,2,664,111,2,1004,156,2,
1231,172,2,1372,203,2,1582,203,3,118,30,3,484,51,3,664,
75,3,1004,108,3,1231,115,3,1372,139,3,1582,140,4,118,32,
4,484,62,4,664,112,4,1004,167,4,1231,179,4,1372,209,4,
1582,214,5,118,30,5,484,49,5,664,81,5,1004,125,5,1231,
142,5,1372,174,5,1582,177]
The number of values of this list can be returned using the length() operator:(%i14) length (List_1);
(%o14) 105
Create another list identified as List_2:(%i15) List_2 : [6,1455,200,6,1607,205];
(List_2) [6,1455,200,6,1607,205]
(%i16) length (List_2);
(%o16) 6
Create a third list identified as List_3:(%i17) List_3 : append (List_1, List_2)$
(%i18) length (List_3);
(%o19) 111
Note that the append () operator does not affect either of the list arguments List_1 or List_2. It merely appends List_2 to List_1 and returns a new list, in this case assigned the identifier List_3.(%i20) length (List_1);
(%o20) 105
(%i21) length (List_2);
(%o21) 6
(%i22) length (List_3);
(%o22) 111
(%i23) List_3;
(%o23) [1,118,30,1,484,58,1,664,87,1,1004,115,1,1231,120,1,1372,
142,1,1582,145,2,118,33,2,484,69,2,664,111,2,1004,156,2,
1231,172,2,1372,203,2,1582,203,3,118,30,3,484,51,3,664,
75,3,1004,108,3,1231,115,3,1372,139,3,1582,140,4,118,32,
4,484,62,4,664,112,4,1004,167,4,1231,179,4,1372,209,4,
1582,214,5,118,30,5,484,49,5,664,81,5,1004,125,5,1231,
142,5,1372,174,5,1582,177,6,1455,200,6,1607,205]
Values (only the last one in this example) can be deleted from a list:(%i24) last (List_3);
(%o24) 205
(%i25) delete (last (List_3), List_3);
(%o25) [1,118,30,1,484,58,1,664,87,1,1004,115,1,1231,120,1,1372,
142,1,1582,145,2,118,33,2,484,69,2,664,111,2,1004,156,2,
1231,172,2,1372,203,2,1582,203,3,118,30,3,484,51,3,664,
75,3,1004,108,3,1231,115,3,1372,139,3,1582,140,4,118,32,
4,484,62,4,664,112,4,1004,167,4,1231,179,4,1372,209,4,
1582,214,5,118,30,5,484,49,5,664,81,5,1004,125,5,1231,
142,5,1372,174,5,1582,177,6,1455,200,6,1607]
The delete (List_3) expression creates a new list object using List_3. List_3 itself is not affected by the delete () operator. Only the resulting list is affected. This is often the case with list operators. An identifier could be used to save the above resulting list if necessary:/* List_3 is the same length */
(%i26) length(List_3);
(%o26) 111
/* The last value of List_3 is the same */
(%i27) List_3[111];
(%o27) 205
(%i28) last(List_3);
(%o28) 205There was only one instance of 205 in List_3, so only that value was deleted from the list of %o25. If there are more than one value then the delete () operator will remove all of those values from the returned list expression:(%i29) NewList_3 : delete(118, List_3)$
/* all five instances of 118 removed from List_3 as NewList_3 */
(%i30) length(NewList_3);
(%o30) 106There are operators to refer to instances of list expressions by ordinal number. Here are few examples:(%i30) FirstThreeList_3:[first(List_3), second(List_3), third(List_3)];
(FirstThreeList_3) [1,118,30]Expressions of list objects can be referenced to create a new list object from the first n expressions of a list, the last n expressions of a list, or the rest remaining after n expressions from the beginning or -n expressions from the end of a list:/* first three expressions of List_3 */
(%i31) firstn(List_3,3);
(%o31) [1,118,30]
/* same three expressions of List_3 using the rest operator with -108 */
(%i32) rest(List_3, -108);
(%o32) [1,118,30]
/* last three expressions of List_3 */
(%i33) lastn(List_3,3);
(%o33) [6,1607,205]
/* same three expressions of List_3 using the rest operator with 108 */
(%i34) rest(List_3, 108);
(%o34) [6,1607,205]Some further examples of the firstn (), lastn () and rest () list operators:/* first 63 expressions of List_3 */
(%i35) firstn (List_3, 63);
(%o35) [1,118,30,1,484,58,1,664,87,1,1004,115,1,1231,120,1,1372,142,1,
1582,145,2,118,33,2,484,69,2,664,111,2,1004,156,2,1231,172,2,
1372,203,2,1582,203,3,118,30,3,484,51,3,664,75,3,1004,108,3,
1231,115,3,1372,139,3,1582,140]
/* last 63 expressions of List_3 */
(%i36) lastn (List_3, 63);
(%o36) [3,664,75,3,1004,108,3,1231,115,3,1372,139,3,1582,140,4,118,32,
4,484,62,4,664,112,4,1004,167,4,1231,179,4,1372,209,4,1582,214,
5,118,30,5,484,49,5,664,81,5,1004,125,5,1231,142,5,1372,174,5,
1582,177,6,1455,200,6,1607,205]
/* first expressions of List_3 after removing the last 63*/
(%i37) rest (List_3, -63);
(%o37) [1,118,30,1,484,58,1,664,87,1,1004,115,1,1231,120,1,1372,142,1,
1582,145,2,118,33,2,484,69,2,664,111,2,1004,156,2,1231,172,2,
1372,203,2,1582,203,3,118,30,3,484,51]Maxima programming expressions can be used with list operators to reference list expressions in various ways. For example, the expression which follows accesses the expressions of a list as lists of three each:(%i38) (L4 : List_4,
for i:(-length (List_4)) + 3 thru 0 step 3 do
(disp (rest (L4, i)),
L4 : rest (L4, 3)),
"")$
[1,118,30]
[1,484,58]
[1,664,87]
[1,1004,115]
[1,1231,120]
[1,1372,142]
[1,1582,145]
[2,118,33]
[2,484,69]
[2,664,111]
[2,1004,156]
[2,1231,172]
[2,1372,203]
[2,1582,203]
[3,118,30]
[3,484,51]
[3,664,75]List operators can be used as arguments of other list operators:/* create a new empty list */
(%i39) newList4 : [];
(newList4) []
/* append the first six & last six expressions of List_4 to the new list */
(%i40) newList4 : append (newList4, firstn (List_4, 6), lastn (List_4, 6))$
/* the new list now has the 12 appended expressions from List_4 */
(%i41) newList4;
(%o41) [1,118,30,1,484,58,3,484,51,3,664,75]
Maxima programming expressions can be used to step through the expressions of lists using list operators to reference "slices" of different lengths:(%i42) L4 : List_4;
(L4) [1,118,30,1,484,58,1,664,87,1,1004,115,1,1231,120,1,1372,142,1,1582
145,2,118,33,2,484,69,2,664,111,2,1004,156,2,1231,172,2,1372,203,
2,1582,203,3,118,30,3,484,51,3,664,75]
(%i43) length (L4);
(%o43) 51
(%i44) (L4 : List_4,
for i : 21 thru 3 step -3 do
print (L4 : lastn (L4, i)," ", length (L4)),
"")$
[2,1004,156,2,1231,172,2,1372,203,2,1582,203,3,118,30,3,484,51,3,664,75] 21
[2,1231,172,2,1372,203,2,1582,203,3,118,30,3,484,51,3,664,75] 18
[2,1372,203,2,1582,203,3,118,30,3,484,51,3,664,75] 15
[2,1582,203,3,118,30,3,484,51,3,664,75] 12
[3,118,30,3,484,51,3,664,75] 9
[3,484,51,3,664,75] 6
[3,664,75] 3
Using the copylist Operator
When an existing list object is assigned to an identifier, a reference to the memory location of the list object is used. This reference to the memory location is transparent from the point of view of the user. However, if this is not accounted for, unintended side effects may occur when assigning another identifier to an existing one that references a list object.
For example, in the expression L4 : List_4 it might be assumed that this results in the list object referenced by List_4 to simply be copied to a new memory location and reference for L4. This is not the case. Both L4 and List_4 refer to the same list object in memory. Consequently, changes to the same list object can be effected by referencing the object using either alias.(%i45) newList4;
(%o45) [1,118,30,1,484,58,3,484,51,3,664,75]
/* The identifier newList4 refers to a location in memory.
The colon (:) assignment operator simply creates an alias
using that same memory reference*/
(%i46) newList5 : newList4;
(newList5) [1,118,30,1,484,58,3,484,51,3,664,75]
/* Two different identifiers for the same memory location and
therefore the same object. Changes to expressions of the list
object using either identifier changes the same object in memory */
(%i47) newList5[1] : 99;
(newList5[1]) 99
(%i48) newList4[1];
(%o48) 99
(%i49) newList4;
(%o49) [99,118,30,1,484,58,3,484,51,3,664,75]
(%i50) newList5;
(%o50) [99,118,30,1,484,58,3,484,51,3,664,75]
This "side effect" is often unwanted. The way to avoid this is by using the copylist () operator instead of the assignment operator:(%i51) newList4 : [1,118,30,1,484,58,3,484,51,3,664,75] ;
(newList4) [1,118,30,1,484,58,3,484,51,3,664,75]
(%i52) newList5 : copylist (newList4);
(newList5) [1,118,30,1,484,58,3,484,51,3,664,75]
/* Same list object expressions, but a different memory reference */
(%i53) newList5[1] : 99;
(newList5[1]) 99
(%i54) newList4[1];
(%o54) 1
Changes to the values of either list will not affect the other list.
+List Object Operators[fourthrreduce]fullsetifysecondappendjoinsetifyassoclastseventhconslengthsixthcopylistlistarithsomecreate_listlistpsortdeletelmaxsublisteighthlminsublist_indicesendconslreducetenthfifthmakelistthirdfirstmembertree_reduceflattenreversexreduce
Refer to the Maximal ManualMaxima Manual Section 7.4.2 Functions and Variables for Lists https://maxima.sourceforge.io/docs/manual/Data-Types-and-Structures.html#Lists for more complete description of operators and variables for Maxima lists.
Performance Considerations for Lists
"Lists provide efficient ways of appending and removing elements. They can be created without knowing their final dimensions. Lisp provides efficient means of copying and handling lists. Also nested lists do not need to be strictly rectangular. These advantages over declared arrays come with the drawback that the amount of time needed for accessing a random element within a list may be roughly proportional to the element’s distance from its beginning."Maxima Manual Section 7.4.3 Performance considerations for Lists https://maxima.sourceforge.io/docs/manual/Data-Types-and-Structures.html#ListsMaxima list objects have many operators for creating, accessing, and changing the expressions of lists. Evaluation times of expressions that involve lists can vary significantly depending on the length of the list objects involved, as well as the nature of any operators used to compose the expressions. This fact might have an influence on the choice of using arrays rather than lists in some cases if evaluation time is a factor to consider. The example expressions that follow involve a list object (identified as s ) of complex number expressions that has a length of 10,000 and two different user-defined functions:(%i55) P1(s) := rectform (product(s[i], i, 1, length (s)))$
(%i56) P2(s) := xreduce (lambda ([a, b], rectform (a * b)), s)$
(%i57) s : makelist (random (1.0) + %i*random (1.0), 10^4)$
(%i58) showtime : true$
Evaluation took 0.0000 seconds (0.0000 elapsed) using 0 bytes.
(%i59) P1(s);
Evaluation took 32.3080 seconds (32.3860 elapsed) using 17944.301 MB.
(%o59) 0.0
(%i60) P2(s);
Evaluation took 0.0620 seconds (0.0620 elapsed) using 8.343 MB.
(%o60) 0.0
(%i61) showtime : false$
Evaluation took 0.0000 seconds (0.0000 elapsed) using 0 bytes.
Arrays
"Maxima's current array/matrix semantics are a mess, I must say, with at least four different kinds of object (hash arrays, explicit lists, explicit matrices, Lisp arrays) supporting subscripting with varying semantics." Maxima: Syntax improvements by Stavros Macrakis
– Stavros Macrakis
"Maxima's concept of arrays, lists, and matrices is pretty confused, since various ideas have accreted in the many years of the project... Yes, this is a mess. Sorry about that. These are all interesting ideas, but there is no unifying framework."Maxima: what does Maxima call an “array”? - answer by Robert Dodier
– Robert Dodier
The various terminology used in the context of arrays undoubtedly can be confusing. For the sake of simplicity and clarity the following categories of array objects are referred to in this book:
Maxima has three array categories :the Maxima mailing list : [Maxima] array and matrix? - answer by Robert Dodier
Declared arrays
Undeclared (hashed) arrays
Lisp arrays
Declared arrays are explicitly created using the array() operator, and unlike lists, which have only one index and are one-dimensional, declared arrays can be multi-dimensional – having one to five indices. The indices for declared array dimensions need not be equal.
Indices for each dimension may be between 0 and the respective declared dimension. Each declared array may be be declared with a specified type, which can be fixnum for integers or flonum for floating-point numbers.
Undeclared (hashed) arrays are created implicitly by assigning an expression to an indexed identifier that does not name an existing list object. An undeclared array (hashed array) can grow dynamically as there is no fixed dimension limit for each index as there is with declared arrays Another difference is that numbers, identifiers, expressions and strings may be used for indices.
Also, there is no fixed limit on the number of dimensions. The only real limitation is practical – the available computer memory and the underlying Lisp environment of the Maxima session. Hashed arrays with only one dimension may be confused with a list object.
It is essential to keep in mind that list objects must always be created before values are assigned or referenced. Assigning an expression to a one-dimensional indexed identifier, that does not reference an existing list, implicitly creates a hashed array.
Lisp arrays are created using the make_array () operator. The array type may be specified as any, flonum, fixnum, or hashed. Lisp array objects may be created as one-dimensional or multi-dimensional as specified by the number of dimension size values used with the make_array () operator. For each dimension value specified, the associated index value can be a number between 0 to 1 less than the respective dimension value. The same or different values may be used for each specified dimension. The hashed type does not require any dimensions. Lisp arrays may be used in cases where memory usage and garbage collection are factors.
Examples of use and elaboration upon the technical details for each of these categories of array object types follows.
+Array Operators and Flagsarrayarraysetapplyrearrayarrayapplyhas_keyremarrayarrayinfofillarraysubvararraymakelistarraysubvarparraysmake_arrayuse_fast_arraystranslate_fast_arrays
Declared Arrays
Declared arrays must be created before being referenced and are of fixed dimensions.(%i1) array (A1, fixnum, 34,2);
(%o1) A1
(%i2) array (A2, flonum, 35,2);
(%o2) A2
(%i3) array (A3, 14,3);
(%o3) A3
(%i4) arrayinfo (A1);
(%o4) [complete,2,[34,2]]
(%i5) arrayinfo (A2);
(%o5) [complete,2,[35,2]]
(%i6) arrayinfo (A3);
(%o6) [declared,2,[14,3]]
The first expression %i1 creates a declared array object named using the identifier A1. This array is two-dimensional – the first index may have values from 0 to 34 and the second index may have values from 0 to 2. This array therefore is 35 by 3 in extent. The array type is declared to be fixnum. As it is, A1 is an not empty array – it has values to reference as will be exemplified below.
Similarly the second expression %i2 creates a declared array object named using the identifier A2. This array is also two-dimensional – the first index may have values from 0 to 35 and the second index may have values from 0 to 2. This array therefore is 36 by 3 in extent. The array type is declared to be flonum. As it is, A2 is not an empty array – it also has values to reference as will be exemplified below.
The third expression %i3 creates a declared array object named using the identifier A3. This array is also two-dimensional – the first index may have values from 0 to 13 and the second index may have values from 0 to 3. This array therefore is 15 by 4 in extent. The array type is not declared. As it is, A3 is an empty array – it has no values to reference as will be exemplified below.
The arrayinfo () operator can be used to return a list object that has values reflecting information about an array. Note that arrays declared without a type will have declared as a value of the list. Arrays declared with fixnum or flonum as the type will have complete as a value of the list. This technical detail is not well-documented. The returned information list also shows a value for the dimension of the array ( 2 in these examples ) and an inner list with values for the maximum index for each dimension.
A look inside each of these declared arrays provides a clue as to why complete is returned instead of declared when the type is specified as fixnum or flonum:/* This array reference has a value of 0 */
(%i7) A1[0,0];
(%o7) 0
/* This array reference has a value of 0.0 */
(%i8) A2[0,0];
(%o8) 0.0
/* This array reference has a value of itself */
(%i9) A3[0,0];
(%o9) A3[0,0]
In fact, all values of the array A1 have been assigned 0, and all values of the array A2 have beenassigned 0.0. However, all values of the array A3 have been not been assigned any value.(%i10) listarray (A1);
(%o10) [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0]
(%i11) listarray (A2);
(%o11) [0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,
0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0]
(%i12) listarray (A3);
(%o12) [#####,#####,#####,#####,#####,#####,#####,#####,#####,#####,#####,
#####,#####,#####,#####,#####,#####,#####,#####,#####,#####,#####,
#####,#####,#####,#####,#####,#####,#####,#####,#####,#####,#####,
#####,#####,#####,#####,#####,#####,#####,#####,#####,#####,#####,
#####,#####,#####,#####,#####,#####,#####,#####,#####,#####,#####,
#####,#####,#####,#####,#####]
Thus, arrays A1 and A2 are complete in this sense, and array A3 is simply declared.
It might seem reasonable, that Maxima would complain if a value was assigned to an array that is at odds with the declared type. Such is not the case however. Once an array is declared with a type, Maxima does not enforce any type checking of values assigned to the array.
The effect of the type (if any) in the declaration is simply to fill the array with values of #####, 0 or 0.0. Subsequently, those values can be replaced by the assignment of different values that will not be checked for the type of value. One way to enforce type checking is by using predicate operators to check values at the point in time of assignment to arrays. For example, using one or more of:
numberp (expr)
floatnump (expr)
integerp (expr)
ratnump (expr)
stringp (obj)
listp (expr)
featurep (a, f)
and possibly others./* A1 was declared as type fixnum */
(%i13) A1[5,2]:3.14159;
(%o13) 3.14159
/* A2 was declared as type flonum */
(%i14) A2[3,0]:7129;
(%o14) 7129
/* A1 which was declared as type fixnum assigned a string */
(%i15) A1[4,1]:"a string";
(%o15) "a string"
/* A2 which was declared as type flonum assigned a math expression*/
(%i16) A2[6,1]:3*x^2 + 4*x +10;
(%o16) 3*x^2+4*x+10
There are other ways to assign values to an array. One way is to read the values from a comma separated (CSV) file using the read_array () operator from the numericalio package.(%i17) load (numericalio);
(%o17) "/app/plugins/maxima/share/maxima/5.50.0/share/numericalio/numericalio.mac"
Below are of the values of the CSV data file tree_data_nh.csv :1,118,30
1,484,58
1,664,87
1,1004,115
1,1231,120
1,1372,142
1,1582,145
2,118,33
2,484,69
2,664,111
2,1004,156
2,1231,172
2,1372,203
2,1582,203
3,118,30
3,484,51
3,664,75
3,1004,108
3,1231,115
3,1372,139
3,1582,140
4,118,32
4,484,62
4,664,112
4,1004,167
4,1231,179
4,1372,209
4,1582,214
5,118,30
5,484,49
5,664,81
5,1004,125
5,1231,142
5,1372,174
5,1582,177
Note that the following expression assumes that the CSV file is located in the Maxima working directory.(%i18) read_array("tree_data_nh.csv", A1, ",");
(%o18) done
(%i19) listarray(A1);
(%o19) [1,118,30,1,484,58,1,664,87,1,1004,115,1,1231,120,1,1372,142,
1,1582,145,2,118,33,2,484,69,2,664,111,2,1004,156,2,1231,172,
2,1372,203,2,1582,203,3,118,30,3,484,51,3,664,75,3,1004,108,
3,1231,115,3,1372,139,3,1582,140,4,118,32,4,484,62,4,664,112,
4,1004,167,4,1231,179,4,1372,209,4,1582,214,5,118,30,5,484,49,
5,664,81,5,1004,125,5,1231,142,5,1372,174,5,1582,177]
Below are of the values of the CSV data file tree_data.csv which has a header row, but is otherwise the same as tree_data_nh.csv:"Tree","age","circumference"
1,118,30
1,484,58
1,664,87
1,1004,115
1,1231,120
1,1372,142
1,1582,145
2,118,33
2,484,69
2,664,111
2,1004,156
2,1231,172
2,1372,203
2,1582,203
3,118,30
3,484,51
3,664,75
3,1004,108
3,1231,115
3,1372,139
3,1582,140
4,118,32
4,484,62
4,664,112
4,1004,167
4,1231,179
4,1372,209
4,1582,214
5,118,30
5,484,49
5,664,81
5,1004,125
5,1231,142
5,1372,174
5,1582,177(%i20) read_array ("tree_data.csv", A2, ",");
(%o20) done
(%i21) listarray (A2);
(%o21) ["Tree","age","circumference",1,118,30,1,484,58,1,664,87,1,1004,
115,1,1231,120,1,1372,142,1,1582,145,2,118,33,2,484,69,2,664,111,
2,1004,156,2,1231,172,2,1372,203,2,1582,203,3,118,30,3,484,51,
3,664,75,3,1004,108,3,1231,115,3,1372,139,3,1582,140,4,118,32,
4,484,62,4,664,112,4,1004,167,4,1231,179,4,1372,209,4,1582,214,
5,118,30,5,484,49,5,664,81,5,1004,125,5,1231,142,5,1372,174,
5,1582,177]The previous values of 0 and 0,0 as well as the assigned values of A1 and A2 have been replaced with the values from the CSV files.
The third array A3 can be used with values from the dataset.csv file below:"Last Name","Sales","Country","Quarter"
"Smith",16753.00,"UK","Qtr 3"
"Johnson",14808.00,"USA","Qtr 4"
"Williams",10644.00,"UK","Qtr 2"
"Jones",1390.00,"USA","Qtr 3"
"Brown",4865.00,"USA","Qtr 4"
"Williams",12438.00,"UK","Qtr 1"
"Johnson",9339.00,"UK","Qtr 2"
"Smith",18919.00,"USA","Qtr 3"
"Jones",9213.00,"USA","Qtr 4"
"Jones",7433.00,"UK","Qtr 1"
"Brown",3255.00,"USA","Qtr 2"
"Williams",14867.00,"USA","Qtr 3"
"Williams",19302.00,"UK","Qtr 4"
"Smith",9698.00,"USA","Qtr 1"(%i22) read_array ("dataset.csv", A3, ",");
(%o22) done
(%i23) listarray (A3);
(%o23) ["Last Name","Sales","Country","Quarter",
"Smith",16753.0,"UK","Qtr 3",
"Johnson",14808.0,"USA","Qtr 4",
"Williams",10644.0,"UK","Qtr 2",
"Jones",1390.0,"USA","Qtr 3",
"Brown",4865.0,"USA","Qtr 4",
"Williams",12438.0,"UK","Qtr 1",
"Johnson",9339.0,"UK","Qtr 2",
"Smith",18919.0,"USA","Qtr 3",
"Jones",9213.0,"USA","Qtr 4",
"Jones",7433.0,"UK","Qtr 1",
"Brown",3255.0,"USA","Qtr 2",
"Williams",14867.0,"USA","Qtr 3",
"Williams",19302.0,"UK","Qtr 4",
"Smith",9698.0,"USA","Qtr 1"]
Values of the array can be referenced by indices as required for some purpose:(%i24) A3[0,0];
(%o24) "Last Name"
(%i25) A3[4,2];
(%o25) "USA"
A row list object can be created using the makelist () operator:(%i26) row1A3: makelist (A3[0, j], j, 0, 3)$
(%i27) row1A3;
(%o27) ["Last Name","Sales","Country","Quarter"]
(%i28) row1A3[1];
(%o28) "Last Name"
(%i29) row15A3: makelist (A3[14, j], j, 0, 3)$
(%i30) row15A3;
(%o30) ["Smith",9698.0,"USA","Qtr 1"]
(%i31) row15A3[2];
(%o31) 9698.0
A list of the indices of the array can be created:(%i32) indices_A3 : arrayinfo (A3)[3];
(indices_A3) [14,3]
The number of indices of array A3 can also be referenced using the length () operator:(%i33) length (arrayinfo(A3)[3]);
(%o33) 2The number of values of the array A3 can be returned using the length () operator:(%i34) length (listarray (A3));
(%o34) 60
The values of the array A3 can be printed as output using the list of values with a loop programming expression:(%i35) for i : 1 thru length (listarray (A3)) do
print ('A3[i] ," = ", listarray (A3)[i])$
A3[1]" = ""Last Name"
A3[2]" = ""Sales"
A3[3]" = ""County"
A3[4]" = ""Quarter"
A3[5]" = ""Smith"
A3[6]" = "16753.0
A3[7]" = ""UK"
A3[8]" = ""Qtr 3"
A3[9]" = ""Johnson"
A3[10]" = "14808.0
A3[11]" = ""USA"
A3[12]" = ""Qtr 4"
A3[13]" = ""Williams"
A3[14]" = "10644.0
A3[15]" = ""UK"
A3[16]" = ""Qtr 2"
A3[17]" = ""Jones"
A3[18]" = "1390.0
A3[19]" = ""USA"
A3[20]" = ""Qtr 3"
A3[21]" = ""Brown"
A3[22]" = "4865.0
A3[23]" = ""USA"
A3[24]" = ""Qtr 4"
A3[25]" = ""Williams"
A3[26]" = "12438.0
A3[27]" = ""UK"
A3[28]" = ""Qtr 1"
A3[29]" = ""Johnson"
A3[30]" = "9339.0
A3[31]" = ""UK"
A3[32]" = ""Qtr 2"
A3[33]" = ""Smith"
A3[34]" = "18919.0
A3[35]" = ""USA"
A3[36]" = ""Qtr 3"
A3[37]" = ""Jones"
A3[38]" = "9213.0
A3[39]" = ""USA"
A3[40]" = ""Qtr 4"
A3[41]" = ""Jones"
A3[42]" = "7433.0
A3[43]" = ""UK"
A3[44]" = ""Qtr 1"
A3[45]" = ""Brown"
A3[46]" = "3255.0
A3[47]" = ""USA"
A3[48]" = ""Qtr 2"
A3[49]" = ""Williams"
A3[50]" = "14867.0
A3[51]" = ""USA"
A3[52]" = ""Qtr 3"
A3[53]" = ""Williams"
A3[54]" = "19302.0
A3[55]" = ""UK"
A3[56]" = ""Qtr 4"
A3[57]" = ""Smith"
A3[58]" = "9698.0
A3[59]" = ""USA"
A3[60]" = ""Qtr 1"
Maxima treats declared arrays as fixed-sized, and individual values are accessed via indices. There is no built-in "row" object — rows must be extracted manually using the makelist () operator or by looping through values of an array.(%i36) array (rowsA3,15);
(%o36) rowsA3
(%i37) arrayinfo (rowsA3);
(%o37) [declared,1,[15]]
(%i38) for i : 0 thru 14 do
(rowsA3[i+1] : makelist (A3[i, j], j, 0, 3),
print ("Row ",i+1,": ",rowsA3[i+1]))$;
"Row "1": "["Last Name","Sales","Country","Quarter"]
"Row "2": "["Smith",16753.0,"UK","Qtr 3"]
"Row "3": "["Johnson",14808.0,"USA","Qtr 4"]
"Row "4": "["Williams",10644.0,"UK","Qtr 2"]
"Row "5": "["Jones",1390.0,"USA","Qtr 3"]
"Row "6": "["Brown",4865.0,"USA","Qtr 4"]
"Row "7": "["Williams",12438.0,"UK","Qtr 1"]
"Row "8": "["Johnson",9339.0,"UK","Qtr 2"]
"Row "9": "["Smith",18919.0,"USA","Qtr 3"]
"Row "10": "["Jones",9213.0,"USA","Qtr 4"]
"Row "11": "["Jones",7433.0,"UK","Qtr 1"]
"Row "12": "["Brown",3255.0,"USA","Qtr 2"]
"Row "13": "["Williams",14867.0,"USA","Qtr 3"]
"Row "14": "["Williams",19302.0,"UK","Qtr 4"]
"Row "15": "["Smith",9698.0,"USA","Qtr 1"]
Maxima does not have a built-in function like delete or unset for removing individual values from arrays using indices. Existing values of an array can be changed by assignment however:(%i52) rowsA3[1] : ["Solomon",1045.0,"USA","Qtr 4"]$
(%i53) for i : 1 thru 15 do
(newRowsA3[i] : rowsA3[i],
print ("Row ",i,": ",newRowsA3[i]))$;
"Row "1": "["Solomon",1045.0,"USA","Qtr 4"]
"Row "2": "["Smith",16753.0,"UK","Qtr 3"]
"Row "3": "["Johnson",14808.0,"USA","Qtr 4"]
"Row "4": "["Williams",10644.0,"UK","Qtr 2"]
"Row "5": "["Jones",1390.0,"USA","Qtr 3"]
"Row "6": "["Brown",4865.0,"USA","Qtr 4"]
"Row "7": "["Williams",12438.0,"UK","Qtr 1"]
"Row "8": "["Johnson",9339.0,"UK","Qtr 2"]
"Row "9": "["Smith",18919.0,"USA","Qtr 3"]
"Row "10": "["Jones",9213.0,"USA","Qtr 4"]
"Row "11": "["Jones",7433.0,"UK","Qtr 1"]
"Row "12": "["Brown",3255.0,"USA","Qtr 2"]
"Row "13": "["Williams",14867.0,"USA","Qtr 3"]
"Row "14": "["Williams",19302.0,"UK","Qtr 4"]
"Row "15": "["Smith",9698.0,"USA","Qtr 1"]
The arrayapply () operator can be used a follows to reference values of a declared array:(%i39) arrayapply (rowsA3,[5]);
(%o39) ["Jones",1390.0,"USA","Qtr 3"]
(%i40) arrayapply (A3, [5,1]);
(%o40) 4865.0The operator fillarray (A, B) assigns values to array A from B, which can be a list or an array. The dimensions of the arrays A and B may not be the same. In that case, array A is filled in "row-major" order. There may not be enough values in B compared to A. In that case, the last value of B is used to for the remaining values of A. There may be too many values of B compared to array A. In that case, the remaining values of B once A is filled are not used. The return value of fillarray () is not the filled array A, but it is the first argument./* Create a declared array B of type fixnum */
(%i41) array (B, fixnum, 9);
(%o41) B
/* list the values of array B */
(%i42) listarray (B);
(%o42) [0,0,0,0,0,0,0,0,0,0]
/* fill array B with the values of the list */
(%i43) fillarray (B, [0,1, 2, 3, 4, 5, 6, 7, 8, 9.0]);
(%o43) B
/* list the values of array B */
(%i44) listarray (B);
(%o44) [0,1,2,3,4,5,6,7,8,9.0]
/* fill array B with the value 1.0 */
(%i45) fillarray (B, [1.0]);
(%o45) B
/* list the values of array B */
(%i46) listarray (B);
(%o46) [1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]
/* fill array B with the first 9 values of the list */
(%i47) fillarray (B, [0,1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,12]);
(%o47) B
/* list the values of array B */
(%i48) listarray (B);
(%o48) [0,1,2,3,4,5,6,7,8,9]
/* Create a declared array D of no type */
(%i49) array (D, 9);
(%o49) D
/* list the values of array D */
(%i50) listarray (D);
(%o50) [#####,#####,#####,#####,#####,#####,#####,#####,#####,#####]
/* fill array D with the value 1.0 */
(%i51) fillarray (D, [1.0]);
(%o51) D
/* list the values of array D */
(%i52) listarray (D);
(%o52) [1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]
/* Create a declared array E of no type */
(%i53) array (E, 15);
(%o53) E
/* fill array E with the values from the newRowsA3 list */
(%i54) fillarray (E, listarray (newRowsA3));
(%o54) E
/* list the values of array D */
(%i55) listarray (E);
(%o55) [["Solomon",1045.0,"USA","Qtr 4"],
["Smith",16753.0,"UK","Qtr 3"],
["Johnson",14808.0,"USA","Qtr 4"],
["Williams",10644.0,"UK","Qtr 2"],
["Jones",1390.0,"USA","Qtr 3"],
["Brown",4865.0,"USA","Qtr 4"],
["Williams",12438.0,"UK","Qtr 1"],
["Johnson",9339.0,"UK","Qtr 2"],
["Smith",18919.0,"USA","Qtr 3"],
["Jones",9213.0,"USA","Qtr 4"],
["Jones",7433.0,"UK","Qtr 1"],
["Brown",3255.0,"USA","Qtr 2"],
["Williams",14867.0,"USA","Qtr 3"],
["Williams",19302.0,"UK","Qtr 4"],
["Smith",9698.0,"USA","Qtr 1"],
["Smith",9698.0,"USA","Qtr 1"]]
/* remove array E */
(%i56) remarray (E);
(%o56) [E]
/* E is no longer an array */
(%i57) arrayinfo (E);
arrayinfo: E is not an array.
-- an error. To debug this try: debugmode(true);
If you need to keep the array but remove only one value, you must reconstruct it without that value. The operator remarray () is the typical way to completely remove an array.
The rearray (A, dim1, dim2 ,…, dimn) operator is used to change the dimensions of an array. The changed array will be filled with the elements of the existing array in row-major order. If the existing array has one or more smaller dimensions, the additional dimension values of the changed array are assigned with a value that depends on the type of the array. If the existing array has one or more larger dimensions, the deficient dimension values of the changed array are lost. The type of the array cannot be changed using this operator. /* list with the values of array B */
(%i58) listarray (B);
(%o58) [0,1,2,3,4,5,6,7,8,9]
/* list with information about array B */
(%i59) arrayinfo (B);
(%o59) [complete,1,[9]]
/* increase the dimension of array B */
(%i60) rearray (B, 12);
(%o60) \#\{Lisp array \[13\]\}
/* list with information about array B with increased dimension */
(%i61) arrayinfo (B);
(%o61) [complete,1,[12]]
/* list with the values of array B */
(%i62) listarray (B);
(%o62) [0,1,2,3,4,5,6,7,8,9,0,0,0]
/* decrease the dimension of array B */
(%i63) rearray (B, 6);
(%o63) \#\{Lisp array \[7\]\}
/* list with information about array B with decreased dimension */
(%i64) arrayinfo (B);
(%o64) [complete,1,[6]]
/* list with the values of array B */
(%i65) listarray (B);
(%o65) [0,1,2,3,4,5,6]
The arraymake () operator returns a value that is an array reference without evaluation. This operator, in spite of the name, does not "make" an array. It returns this reference as a value whether or not an array exists as an object:/* the arraymake operator returns the unevaluated reference to A */
(%i66) arraymake (A, [i,j]);
(%o66) A[i,j]
/* however array A is not an array object*/
(%i67) arrayinfo (A);
arrayinfo: A is not an array.
-- an error. To debug this try: debugmode(true);
/* Maxima sees A[j,k] as a reference to an undefined memoizing function */
(%i68) op (arraymake (A, [i,j]));
(%o68) A
However, from a practical point of view, this operator is used with existing arrays:(%i69) listarray (newRowsA3);
(%o69) [["Solomon",1045.0,"USA","Qtr 4"],
["Smith",16753.0,"UK","Qtr 3"],
["Johnson",14808.0,"USA","Qtr 4"],
["Williams",10644.0,"UK","Qtr 2"],
["Jones",1390.0,"USA","Qtr 3"],
["Brown",4865.0,"USA","Qtr 4"],
["Williams",12438.0,"UK","Qtr 1"],
["Johnson",9339.0,"UK","Qtr 2"],
["Smith",18919.0,"USA","Qtr 3"],
["Jones",9213.0,"USA","Qtr 4"],
["Jones",7433.0,"UK","Qtr 1"],
["Brown",3255.0,"USA","Qtr 2"],
["Williams",14867.0,"USA","Qtr 3"],
["Williams",19302.0,"UK","Qtr 4"],
["Smith",9698.0,"USA","Qtr 1"]]
(%i70) arraymake (newRowsA3, [4]);
(%o70) newRowsA3[4]
(%i71) ''%;
(%o71) ["Williams",10644.0,"UK","Qtr 2"]
(%i72) arraymake (newRowsA3, [i]);
(%o72) newRowsA3[i]
(%i73) ''%, i = 6;
(%o73) ["Brown",4865.0,"USA","Qtr 4"]
The operator arraymake (x, [i]) constructs the expression x[i], but does not evaluate it.
The operator subvar (x, i) evaluates the indexed expression x[i], and evaluates its arguments. (%i74) listarray (B);
(%o174) [0,1,2,3,4,5,6]
(%i75) x : B;
(x) B
(%i76) i : 4;
(i) 4
(%i77) subvar (x, i);
(%o77) 4
The operator subvarp (expr) returns a value of true if expr is a indexed identifier, and false otherwise. This operator evaluates the argument expr. (%i78) z[p];
(%o78) z[p]
(%i79) subvarp (z[p]);
(%o79) true
(%i80) x[i];
(%o80) 4
(%i81) subvar (x, i)
(%o81) 4
(%i82) w : z[p];
(w) z[p]
(%i83) subvarp (w);
(%o83) true
The expression arrays returns a list of allocated arrays: (%i84) arrays;
(%o84) [A3,A2,A1,rowsA3,newRowsA3,B,D]
The operator arraysetapply (A, [i1,i2,…,in], x) assigns the value of expression x to A[i1,i2,…,in], where A is an array and i1,i2,…,in are integers.(%i85) arraysetapply (newRowsA3, [8], ["George",20818.0,"UK","Qtr 4"]);
(%o85) ["George",20818.0,"UK","Qtr 4"]
Undeclared Arrays
Maxima declared arrays and Maxima undeclared arrays will likely be the two types of data structures used by most users. Maxima undeclared arrays are also referred to as hashed arrays. For both of these two array types, memory allocation and structure occur "behind the curtain" of the Maxima user level, and "the man behind the curtain" is Lisp.
Of these two types, hashed arrays are the most flexible from the user point of view, all other factors being equal:
The indices allowed for hashed arrays can be expressions,
The dimensions of a hashed array are dynamic and not fixed, and
Hashed arrays are relatively easier to create.
In fact, hashed arrays can be somewhat too easy to create. In the examples of the previous section involving declared arrays, identifiers were used as names for arrays. Here is the list of allocated arrays:(%i86) arrays;
(%o86) [A3,A2,A1,rowsA3,newRowsA3,B,D,my_list,h]The array information for array newRowsA3 is shown below:(%i87) arrayinfo(newRowsA3);
(%o87) [hashed,1,[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15]]So the array newRowsA3 is not a declared array. it was created as part of a loop programming expression repeated below:(%i53) for i : 1 thru 15 do
(newRowsA3[i] : rowsA3[i],
print("Row ",i,": ",newRowsA3[i]))$;When the first loop block was executed with i : 1, the assignment expression:
newRowsA3[i] : rowsA3[i]
created a hashed array named newRowsA3 and assigned newRowsA3[1] the value of rowsA3[1] automatically without any declaration. After that the hashed array dynamically allocated memory for values of the loop identifier i from 2 through 15 with each successive pass in the loop expression. Since hashed arrays are undeclared, and their associated dimensions are not fixed, but rather increase dynamically, expressions can be added to hashed arrays without creating a error message. These added values can be can be any valid Maxima expression, and the value of the indices used are not limited to zero and nonzero integers: (%i88) newRowsA3[16] : ["Maisel",12785.0,"USA","Qtr 2"];
(newRowsA3[16]) ["Maisel",12785.0,"USA","Qtr 2"]
(%i89 arrayinfo(newRowsA3);
(%o89) [hashed,1,[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16]]
(%i90) newRowsA3[z] : 2*sin(y) ^2 + 3*sin(x)^3;
(newRowsA3[z]) 2*sin(y)^2+3*sin(B)^3
(%i91) arrayinfo(newRowsA3);
(%o91) [hashed,1,[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[z]]So one point to glean from this example is that all that is required to create a hashed array is an assignment expression to an indexed identifier, but it does require this assignment. If for some reason, a declared array is intended, but not declared using the array operator (as in the instance of newRowsA3), then a hashed array will be created at the first assignment of an expression to an indexed identifier.
This issue can also occur in cases where reference to a list value is intended, but the list has not been created. Lists are one-dimensional objects by definition. A reference using an identifier with a single index intended to be a list, with no list existing named by that identifier, can inadvertently create a hashed array.(%i91) Alist[2] : 2*sin(y) ^2 + 3*sin(s)^3;
(Alist[2]) 2*sin(y)^2+3*sin(s)^3
(%i92) listp(Alist);
(%o92) false
(%i93) arrayinfo(Alist);
(%o93) [hashed,1,[2]]The identifier Alist is a name for a hashed array and not a list.
However, hashed arrays, like declared arrays can, be multi-dimensional: (%i94) a_hashed_array [j,k,l] : [12,s^(1/3), %i];
(%o94) [12,s^(1/3),%i]
(%i95) arrayinfo(a_hashed_array);
(%o95) [hashed,3,[j,k,l]]The array operator can be used to transform a hashed array into a declared array:(%i96) another_hashed_array[ 0, 2, 3] : 55;
(%o96) 55
(%i97) array (another_hashed_array, 2, 4, 5);
(%o97) another_hashed_array
(%i98) arrayinfo(another_hashed_array);
(%o98) [declared,3,[2,4,5]]From the user point of view, hashed arrays and declared arrays typically can be used for the same purpose. If dynamic dimensions or index expression flexibility are factors, then hashed arrays are likely to be a better choice. Many of the operators that can use declared arrays as arguments, may also be used with hashed arrays.
However, it should be noted at this point, that merely using a indexed identifier alone without an assignment, will not create a hashed array. Instead, Maxima interprets an expression so composed as an identifier for an undefined memoizing function, which is the next topic.
Memoizing Functions
Assignment using an indexed identifier implicitly creates an undeclared (hashed) array. However, entering an expression that is an unassigned indexed identifier, from the point of view of Maxima, creates the identifier of a "memoizing" function. Memoizing functions are technically not an array topic, as these functions fall under the category of function definition. However, these functions have one foot in the function category and the other foot in the array category.
Memoizing functions are defined in a manner similar to other user-defined functions, for example:
f(x, y, z) := x^2 + y^2 + z^2;
But instead of the arguments being enclosed in parentheses, they are enclosed using brackets:
f[x, y, z] := x^2 + y^2 + z^2;
However, memoizing functions may be referred to as array functions, and it is the case that these functions from the point of view of aggregate data structures are hashed arrays. So these are mentioned and exemplified here for that reason:/* reference an indexed identifier h without assignment */
(%i99) h[x];
(%o99) h[x]
/* check identifier h as an array */
(%i100) arrayinfo (h);
arrayinfo: h is not an array.
-- an error. To debug this try: debugmode(true);
/* check identifier h as a function */
(%i101) op(h[x]);
(%o101) h
/* define h[x] as a memoizing function */
(%i102) h[x] := 5*x^3 + sin(x);
(h[x]) h[x]:=5*x^3+sin(x)
/* check identifier h[x] as a function */
(%i103) op(h[x]);
(%o103) "+"
/* check identifier h as an array */
(%i104) arrayinfo (h);
(%o104) [hashed,1,[x]]
/* check the value of identifier h[x] */
(%i105) h[x];
(%o105) sin(x)+5*x^3
/* evaluate identifier h[x] with the argument 3*%pi */
(%i106) h[3*%pi];
(%o106) 135*%pi^3
/* list the values of identifier h as an array */
(%i107) listarray(h);
(%o107) [135*%pi^3,sin(x)+5*x^3]
/* check identifier h as an array */
(%i108) arrayinfo (h);
(%o108) [hashed,1,[3*%pi],[x]]A defined memoizing function stores its value the first time it is referenced with arguments, and returns the stored value, without evaluation, when the same arguments are used with that function subsequently. The stored value is "frozen" after the initial evaluation. Thus, it behaves like a hashed array. The arguments of a memoizing function also serve as indices of the associated hashed array.
Lisp Arrays
Lisp arrays are created using the make_array (type, dim1, dim2, …, dimn) operator. Perhaps a better name for this operator would be make_lisp_array (), which admittedly is a longer name.
This operator creates and has a return value of a Lisp array. The array type may be any, flonum, fixnum, or hashed . The functional type referred to in the Maxima Manual appears to have been deprecated, and may result in an error message if used. There can be n indices, and where the associated indices can be a value from 0 to dimi -1, and i can have a value of one of 1 to n inclusive.
This operator can be used as an alternative to the array () operator. The advantage of using make_array() is that the return value of make_array() does not have a name. Once the Maxima identifier that is assigned to the array return value is no longer assigned, the array memory allocation is released, and the allocated memory can be garbage collected. What it is being referred to that Lisp arrays created using make_array() do not have names? /* create a undeclared Maxima hashed array */
(%i109) arrayA1 [1] : 55;
(%o109) 55
/* check the information about this undeclared Maxima hashed array */
(%i110) arrayinfo (arrayA1);
(%o110) [hashed,1,[1]]
/* check the value of this undeclared Maxima hashed array */
(%i111) arrayA1;
(%o111) arrayA1 /* the name */
/* create a Lisp hashed array */
(%i112) arrayA2 : make_array (hashed);
(arrayA2) "#<HASH-TABLE :TEST EQUAL :COUNT 1 {1202987DC3}>"
/* check the information about this Lisp hashed array */
(%i113) arrayinfo (arrayA2);
(%o113) [hash_table,1]
/* check the value of this Lisp hashed array */
(%i114) arrayA2;
(%o114) "#<HASH-TABLE :TEST EQUAL :COUNT 1 {1202987DC3}>" /* no name */
(%i115) listarray (arrayA2);
(%o115) [] /* this hashed array is empty */So Lisp arrays do not have a name as a value. So, Maxima identifiers assigned to a Lisp array have no name for the array as a value. These identifiers have the reference to the array itself as a value, Lisp hashed arrays created using make_array() with the hashed type specified, do not use dimensions. Lisp hashed arrays are often referred to as hashed tables, and their indices are called keys.
An example of a Lisp array with the type any follows:
/* make a Lisp 3 by 4 array of type any */
(%i116) arrayA3 : make_array (any, 3,4);
(arrayA3) \#\{Lisp array \[3\\,4\]\}
(%i117) arrayinfo (arrayA3);
(%o117) [declared,2,[2,3]]
(%i118) listarray(arrayA3);
(%o118) [false,false,false,false,false,false,false,false,false,false,false,false]
(%i119) arrayA3[1,1] : "a string" ;
(%o119) "a string"
(%i120) listarray(arrayA3);
(%o120) [false,false,false,false,false,"a string",
false,false,false,false,false,false]Contrary to statements made in some sources, type checking is is not accomplished automatically, and will not cause Maxima to return an error message, as the Lisp array with the type fixnum which follows exemplifies:(%i121) arrayA4 : make_array (fixnum, 3,4);
(arrayA4) \#\{Lisp array \[3\\,4\]\}
(%i122) arrayinfo (arrayA4);
(%o122) [declared,2,[2,3]]
(%i123) listarray (arrayA4);
(%o123) [0,0,0,0,0,0,0,0,0,0,0,0]
(%i124) arrayA4[0,2] : 3.14159;
(%o124) 3.14159
(%i125) arrayA4[0,0] : "a string";
(%o125) "a string"
/* value types not checked and no error message */
(%i126) listarray (arrayA4);
(%o126) ["a string",0,3.14159,0,0,0,0,0,0,0,0,0]As was the case with declared arrays, there is no type checking and no error messages associated with assigned values of the array based on the declared type. The declared array type simply determines initial fill values, if any.
Hashed arrays and Lisp hash tables have an operator in common:
has_key (A, L)
This expression has a a value of true, if the hashed array or Lisp hash table A has the key (index) or keys (indices) of the list object L. Otherwise the expression has a value of false.
The array A must be a hashed (undeclared) array, a Lisp hash table value returned by the make_array operator, or created as an undeclared array with use_fast_arrays assigned the value true.
If array A has only one key, that key must be the value of a list.(%i127) arrayinfo (newRowsA3);
(%o127) [hashed,1,[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],
[11],[12],[13],[14],[15],[16],[z]]
(%i128) has_key (newRowsA3, [z]);
(%o128) true
(%i129) has_key (newRowsA3, [3]);
(%o129) true
(%i130) has_key(newRowsA3, [0]);
(%o130) false
(%i131) lisp_Array_1 [x];
(%o131) 3*x^2
(%i132) arrayinfo (lisp_Array_1);
(%o132) [hash_table,1,x]
(%i133) has_key (lisp_Array_1, [x]);
(%o133) true
(%i134) has_key (lisp_Array_1, [y]);
(%o134) false
Arrays and Features
Maxima arrays can be assigned features in a manner similar to other identifiers:(%i135) array (a6,fixnum, 5);
(%o135) a6
(%i136) arrayinfo (a6);
(%o136) [complete,1,[5]]
(%i137) declare (a6, [real, noninteger]);
(%o137) done
(%i138) featurep (a6, real);
(%o138) true
(%i139) featurep (a6, noninteger);
(%o139) true
(%i140) a6[2] : 2;
(a6[2]) 2
(%i141) listarray (a6);
(%o141) [0,0,2,0,0,0]However, once again, declaring an array to have one or more features does not result in type checking of values assigned to an array. Perhaps the best way to ensure types of values assigned to arrays is type-checking of expressions using predicate operators prior to assignment to arrays.
Array Memory Storage Considerations
The following from the Maxima ManualMaxima Manual Section 7.5.1 - Functions and Variables for Arrays https://maxima.sourceforge.io/docs/manual/Data-Types-and-Structures.html#Functions-and-Variables-for-Arrays is quoted here for the purpose of establishing the context of that which follows:"Option variable: use_fast_arrays ¶
Default value: false
When use_fast_arrays is true, arrays declared by array are values instead of properties, and undeclared arrays (hashed arrays) are implemented as Lisp hashed arrays.
When use_fast_arrays is false, arrays declared by array are properties, and undeclared arrays are implemented with Maxima’s own hashed array implementation.
Note that the code use_fast_arrays switches to is not necessarily faster than the default one; Arrays created by make_array are not affected by use_fast_arrays."
This Maxima Manual entry requires some elaboration in order to have a sufficient understanding of the effect of the use_fast_arrays "flag" – referred to as an "Option Variable" in the above quotation.
The first point to note is that the default value of this flag is false. So, if users take no action to assign this flag to the value true, then declared arrays created using the array() operator are treated as properties bound to a symbol, and behave in the manner previously described. Likewise, undeclared arrays are a Maxima array implementation, and behave as legacy property hash tables, and these also behave as previously described.
If action is taken to enter an expression that assigns the value true to this flag, then Maxima treat arrays as standard Lisp values rather than object properties:(%i142) use_fast_arrays;
(%o142) false
(%i143) array (A1,fixnum, 10);
(%o143) A1
(%i144) arrayinfo (A1);
(%o144) [complete,1,[10]]
(%i145) H1[1] : 1729;
(H1[1]) 1729
(%i146) arrayinfo (H1);
(%o146) [hashed,1,[1]]
(%i147) use_fast_arrays : true;
(use_fast_arrays) true
(%i148) array (A2, fixnum, 10);
(%o148) \#\{Lisp array \[11\]\}
(%i149) A2;
(%o149) \#\{Lisp array \[11\]\}
(%i150) H2[1] : 1729;
(H2[1]) 1729
(%i151) H2;
(%o151) "#<HASH-TABLE :TEST EQUAL :COUNT 2 {1203AD15A3}>"
(%i152) arrayinfo (H2);
(%o152) [hash_table,1,1] The use_fast_arrays flag has no effect on arrays created using the make_array () operator.
Note the following:
Assigning use_fast_arrays : true does not speed up execution for all operations. It primarily optimizes memory footprint by minimizing cell allocations. The main benefit is optimization of memory footprints and structural representation inside Lisp.
If compiling Maxima expressions to native Lisp using the translate () operator, pair this flag with translate_fast_arrays : true. This flag instructs the translator to compile arrays as value-based.
Structures
Structures, although not an array data type is an aggregation data type. By "aggregation" it is meant a data type that references other data types by reference to a name and some method of accessing individual values of instances of that aggregation by means of indices used to reference values individually.
Thus, as has been previously described for lists, hashed arrays, Lisp arrays, and declared arrays are all aggregation data types with syntactical means to refer to individual values of a named aggregation instance by way of indices, which are sometimes also referred to as subscripts or keys.
As examples:
An existing list named L1 has values that can be accessed as L1[1], L[2], L[3], and so forth
An existing hashed array with two indices ("2D") named ha has values that can accessed as ha[1,1], ha[1,2],..., ha[2,1], ha[2,2], and so forth.
An existing declared array with three indices ("3D") named da has values that can be accessed as da[1,1,1], da[1,1,2],...,da [2,1,1], ..., da[3,1,1], and so forth.
What makes the Maxima structure data type different from these array data types is the form of definition used and the means of accessing values of an instance of this type of aggregation. The Maxima structure data type seems to be the closest type of data that resembles an object in some other general-purpose programming languages.
The structure type of data allows the creation of named entities with attributes, and named instances of those entities and the assignment of values to those attributes. Thus the Maxima structure data type is used to define custom, named composite data types that group related fields together under a single identifier. Structures enhance code organization and readability, especially when modeling real-world entities in symbolic computations. They are useful for managing complex data in programs involving algebraic manipulations, simulations, or data modeling.
Structures are created using the defstruct operator, which defines a template with named attributes. Once defined, instances of the structure are created using the new operator, and attributes are accessed via the @ operator.
So in the interest of taking advantage of this information, consider a well-defined set of people. The people of this set may have many diverse attributes that might be of interest - name, age ,and city of residence as examples. A class of people of the set of interest with these attributes can be defined as follows: (%i153) defstruct (person (name, age, city));
(%o153) [person(name,age,city)]
(%i54) structures;
(%o154) [person(name, age, city)]
(%i155) p : new (person ("Alice", 30, "New York"));
(p) person(name="Alice",age=30,city="New York")
(%i156) p@name;
(%o156) "Alice"
(%i157) p@city;
(%o157) "New York"
(%i158) kill(p@city);
(%o158) done
/* If the attribute a in x has not been assigned a value, or the value
has been "killed," then x@a evaluates to itself */
(%i159) p@city;
(%o159) p@city
(%i160) p;
(%o160) person(name="Alice",age=30,city)
(%i161) p@city : "Rochester";
(%o161) "Rochester"
(%i162) p;
(%o162) person(name="Alice",age=30,city="Rochester")
(%i163) defstruct (sales(LastName, Sales , Country , Quarter) );
(%o163) [sales(LastName, Sales, Country, Quarter)]
(%i164) structures;
(%o164) [person(name, age, city), sales(LastName, Sales, Country, Quarter)]
(%i165) s : new (sales ("Smith",16753.00,"UK","Qtr 3"));
(%o165) sales(LastName=Smith, Sales=16753.0, Country=UK, Quarter=Qtr 3)
(%i166) s@Sales;
(%o166) 16753.0
(%i167) s@Sales : 17375.0;
(%o167) 17375.0
(%i168) s;
(%o168) sales(LastName=Smith, Sales=17375.0, Country=UK, Quarter=Qtr 3)