Contents Previous Next Index
4 Basic Data Structures
The appropriate use of data structures is an important part of writing efficient programs. Maple provides various data structures that can be used to help make your programs efficient.
4.1 In This Chapter
Defining and manipulating sets, lists, tables, Arrays, records, stacks, and queues
Converting between data structures
Mathematical versus programmer indexing for Arrays
Performance comparisons of data structures
4.2 Introduction
Maple provides various data structures that you can use for programming and interacting with Maple functions. This chapter focuses on the use of data structures in programming. However, the sections Lists and Sets may be useful for users who want to construct arguments for Maple functions.
Maple has many data structures that provide similar functionality, but certain data structures are better suited for certain types of operations. Therefore, when choosing which data structures to use, it is important to select a structure that performs well on the operations used in your code.
Many aspects affect the performance of data structures. However, in Maple, the provided data structures can be divided into two basic classes: mutable and immutable. The mutable data structures can be modified, that is, the values they store can change. The immutable data structures cannot be changed after they are created. Instead, copies of these structures can be made with different contents. This difference in behavior can have significant impact on the performance of code that uses these structures.
4.3 Immutable Data Structures
Immutable data structures can be useful when storing a fixed collection of elements. Also, because immutable structures are more compact than mutable data structures, especially when storing a small number of elements, they are more memory efficient.
Immutable structures are created with their contents fixed. This means that they cannot be modified in-place. When you change an immutable data structure, a new copy is created with the modified contents and stored as a distinct object in memory. Thus, immutable structures may not be the correct choice if you want to modify the contents of a structure.
In Maple, there are two basic immutable data structures: lists and sets.
Lists
A list stores an ordered sequence of expressions. The ordering of the elements in a list is fixed when the list is created. Lists, in contrast to sets, will maintain duplicate elements.
Creating Lists
The easiest way to create a list is to enclose a sequence of expressions in square brackets ([]). A sequence of expressions is a series of comma-separated expressions.
[ sequence ]
This creates a list that contains the elements of sequence in the specified order. In the case where sequence is empty, [] represents an empty list. Compare the results of these examples to those in the Sets section.
[x, y, y];
x,y,y
[a, 1, b, 2];
a,1,b,2
[y[1], x, x[1], y[1]];
y1,x,x1,y1
The elements of a list can be any expressions, even other lists.
L := [[1], [2, a], [X, Y, Z]];
L≔1,2,a,X,Y,Z
In Maple, nested lists whose inner lists have the same number of elements have a special name, listlist.
M := [[a,b], [1,2], [3, 4]];
M≔a,b,1,2,3,4
type(M, list);
true
type(L, listlist);
false
type(M, listlist);
Many Maple functions return sequences. Thus, enclosing a call to one of those functions in square brackets [] creates a list. For example, the seq command generates sequences.
[ seq( x^j, j=1..3) ];
x,x2,x3
The op command can be used to extract the sequence of elements in a list.
L := [1,2,3];
L≔1,2,3
op(L);
1,2,3
Thus op can be used to create new lists based on existing lists. For example, you can create a new list with an additional element added to the start of the list.
L2 := [ 0, op(L) ];
L2≔0,1,2,3
A list with another element added to the end of the list can be created in a similar way.
L3 := [ op(L2), 4 ];
L3≔0,1,2,3,4
Multiple lists can also be combined into a single list.
L4 := [ op(L), op(L2), op(L3) ];
L4≔1,2,3,0,1,2,3,0,1,2,3,4
Accessing Data Stored in a List
The selection operation, [], can be used to read an element from a list.
L[1];
1
L[2];
2
L[3];
3
You can also specify a range in the selection operation to extract a sublist containing the elements that are indexed by that range.
L := [ seq( i^2, i=1..10 ) ];
L≔1,4,9,16,25,36,49,64,81,100
L[3..6];
9,16,25,36
L[5..8];
25,36,49,64
While it is possible to make an assignment to a list index, this operation can be inefficient since it creates a new list. In fact, assignment to a large list is not permitted in Maple and will produce an error. Assigning a list element is a common error, so if you find yourself wanting to do this, consider using a mutable data structure instead. For more information, see Mutable Data Structures.
L := [1,2,3]:
L[1] := 3;
L≔3,2,3
L;
3,2,3
L is now a new list with a different element at index 1. Thus, assigning to a single element of a list causes the entire list to be copied in the same way as using the subsop command. In fact, the previous example is equivalent to the following except in how the result is displayed.
L := subsop(1=3, L);
If you attempt to assign to an index to a large list, an error will occur. Therefore, if you need to make a copy of a list with one changed element, it is recommended that you use the subsop command instead.
LL := [ seq( i, i=1..200 ) ]:
LL[1] := -1;
Error, assigning to a long list, please use Arrays
subsop(1=-1, LL);
−1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200
Determining If an Element Is in a List
To test if an expression is contained in a list, use the member function.
member( 1, [ 1,2,3 ] );
member( 1, [ 2,3,4 ] );
You can also use the in operator.
evalb( 1 in [1,2,3] );
evalb( 1 in [2,3,4] );
Getting the Number of Elements in a List
To find the length of a list, use the numelems command.
numelems( [ 1,2,3 ] );
numelems( [ 1,2,3,4,5 ] );
5
numelems( [ seq( i, i=1..127 ) ] );
127
This can be useful for many tasks, for example, using lists in a loop. For more information on selectremove, see Filtering Data Structure Elements.
L := [seq( i, i=2..100)]:
divisor := 2:
while ( numelems( L ) > 0 ) do divisible, L := selectremove( i->(i mod divisor = 0), L ): n := numelems( divisible ); if ( n > 0 ) then printf( "%d integer%s whose smallest prime divisor is %d\n", n, `if`( n > 1, "s", "" ), divisor ): end if; divisor := nextprime( divisor ); end do:
50 integers whose smallest prime divisor is 2 17 integers whose smallest prime divisor is 3 7 integers whose smallest prime divisor is 5 4 integers whose smallest prime divisor is 7 1 integer whose smallest prime divisor is 11 1 integer whose smallest prime divisor is 13 1 integer whose smallest prime divisor is 17 1 integer whose smallest prime divisor is 19 1 integer whose smallest prime divisor is 23 1 integer whose smallest prime divisor is 29 1 integer whose smallest prime divisor is 31 1 integer whose smallest prime divisor is 37 1 integer whose smallest prime divisor is 41 1 integer whose smallest prime divisor is 43 1 integer whose smallest prime divisor is 47 1 integer whose smallest prime divisor is 53 1 integer whose smallest prime divisor is 59 1 integer whose smallest prime divisor is 61 1 integer whose smallest prime divisor is 67 1 integer whose smallest prime divisor is 71 1 integer whose smallest prime divisor is 73 1 integer whose smallest prime divisor is 79 1 integer whose smallest prime divisor is 83 1 integer whose smallest prime divisor is 89 1 integer whose smallest prime divisor is 97
Sorting a List
The sort command can create a new list with sorted elements from any given list. By default, sort arranges elements in ascending order.
sort( [ 4,2,3 ] );
2,3,4
The sort command can also accept a second argument that specifies the ordering to use when sorting the elements.
sort( [4,2,3], `>` );
4,3,2
Applying a Function to the Contents of a List
It is often useful to be able to apply a function to all the elements of a list. The map command performs this operation in Maple.
L := [ seq( Pi*i/4, i=0..3 ) ]:
map( sin, L );
0,22,1,22
map( cos, L );
1,22,0,−22
Maple provides other operations that can work with the members of a list, such as add and mul.
add( i, i in [ seq( j, j=1..100 ) ] );
5050
mul( i^2, i in [ 1,2,3,4,5,6,7,8,9,10 ] );
13168189440000
Finally, a for loop can be combined with the in operator to loop over the contents of a list.
for i in [1,2,3,4] do print( i^2 ); end do;
4
9
16
Sets
A set is an unordered sequence of unique expressions. When a set is created, Maple reorders the expressions to remove duplicate values and to make certain operations faster.
Creating Sets
The easiest way to create a set is to enclose a sequence of expressions in braces ({}).
{ sequence }
When Maple creates the set, it performs automatic simplification. This process creates a set that contains the elements of sequence; however, during the automatic simplification process, any duplicate elements are removed and the remaining elements are reordered.
Compare the results of these examples to those in the Lists section.
{x, y, y};
x,y
{a, 1, b, 2};
1,2,a,b
{y[1],x,x[1],y[1]};
x,x1,y1
Similar to lists, sets can be created using functions such as seq that return sequences.
{ seq( i mod 3, i=1..10 ) };
0,1,2
Again, similar to lists, the op command can be used to extract the sequence of elements in a set.
S := {1,2,3};
S≔1,2,3
op(S);
However, unlike lists, Maple provides operations for set arithmetic, so for sets op is somewhat less important.
Set Arithmetic
Maple provides operators for mathematical set manipulations: union, minus, intersect, and subset. These operators allow you to perform set arithmetic in Maple.
s := {x,y,z};
s≔x,y,z
t := {y,z,w};
t≔w,y,z
s union t;
w,x,y,z
s minus t;
x
s intersect t;
y,z
s subset t;
s subset {w,x,y,z};
Accessing Data Stored in a Set
The selection operation, [], can be used to read an element from a set. However, unlike lists, the order in which the elements are specified when creating the set may not correspond to the order they are accessed by indexing.
S := {3,2,1}:
S[1];
S[2];
S[3];
Unlike lists, you cannot use the selection operation to create new sets.
S[1] := 4;
Error, cannot reassign the entries in a set
You can specify a range in the selection operation to extract the elements indexed by the range.
S2 := { seq( i^2, i=1..10 ) };
S2≔1,4,9,16,25,36,49,64,81,100
S2[3..6];
S2[5..8];
Determining If an Element Is in a Set
To test if an element is contained in a set, use the member function.
member( 1, {1,2,3} );
member( 1, {2,3,4} );
evalb( 1 in {1,2,3} );
evalb( 1 in {2,3,4} );
Getting the Number of Elements in a Set
To find the number of elements in a set, use the numelems command.
numelems( {1,2,3} );
numelems( {1,2,3,4,5} );
numelems( {seq( i, i=1..127 )} );
In this example, the features of sets are used to test Collatz's conjecture on the first million integers. Collatz's conjecture states that given any integer, i, if the following function is applied repeatedly, the result will eventually be 1.
collatz := proc( i ) if ( i = 1 ) then 1; elif ( type( i, even ) ) then i/2; else 3*i+1; end if; end proc:
Begin with a set S that consists of the integers from 1 to 1 million. Under repeated application of collatz, as numbers converge to 1, the set automatically removes duplicate values, until eventually there is only 1 element left. For more information on the use of map, see Applying a Function to the Contents of a Set.
S := {seq( i, i=1..1000000)}:
while ( numelems( S ) > 1 ) do S := map( collatz, S ): end do:
S;
Applying a Function to the Contents of a Set
As with lists, it can be useful to apply a function to all of the elements of a set. The map command works on sets, as it does with lists.
S := { seq( Pi*i/4, i=0..3 ) }:
map( sin, S );
0,1,22
map( cos, S );
0,1,−22,22
Notice that when applying a function to a set, the output is also a set, which means the elements are reordered and duplicate elements are removed.
add( i, i in { seq( j, j=1..100 ) } );
mul( i^2, i in { 1,2,3,4,5,6,7,8,9,10 } );
Finally a for loop can be combined with the in operator to loop over the contents of a set. Note that the set has been reordered.
for i in {1,4,3,2} do print( i^2 ); end do;
4.4 Mutable Data Structures
Mutable data structures are structures whose contents can be changed.
The most flexible mutable data structure provided by Maple is the table.
Tables
A table stores a collection of index/entry pairs. For a given index, the table contains a particular value, called an entry. Index/entry pairs can be created or removed, or the value associated with an index can be modified.
Creating Tables
A new table can be created by calling the table function.
t := table();
t≔table⁡
With no arguments, table creates a new empty table. To create a table that contains certain index/entry pairs, specify the pairs as a list of equations. The left-hand side of an equation is the index and the right-hand side is the entry.
t := table( [ 1=2, a=b, f(x)=y ] );
t≔table⁡1=2,a=b,f⁡x=y
If the given list contains one or more expressions that are not equations, the list is treated as a list of entries and the indices are the positions of the entries in the list (1, 2, 3, ...).
t := table( [ a, b, c=d ] );
t≔table⁡1=a,2=b,3=c=d
Note that c=d is treated as a entry and not an index/entry pair.
Tables are also created implicitly when you assign to an indexed name.
t2[new] := 10;
t2new≔10
eval(t2);
table⁡new=10
Accessing Stored Values
Table indexing is performed using the selection operation, []. To extract data from a table, specify an index in square brackets. The corresponding entry is returned.
t := table( [1=2,a=b,f(x)=y] );
t[1];
t[a];
b
t[f(x)];
y
If the table does not contain a entry associated with the index, an unevaluated table reference is returned.
t[2];
t2
The selection operation can also be used to add new index/entry pairs to the table.
t[2] := 3;
t2≔3
t[c] := d;
tc≔d
t[sin(x)] := 1;
tsin⁡x≔1
t[c];
d
t[sin(x)];
Removing an Element
The best way to remove an element from a table is to call the unassign function.
t[1] := x;
t1≔x
t[sin(x)] := y;
tsin⁡x≔y
unassign( 't[1]' );
t1
unassign( 't[sin(x)]' );
tsin⁡x
The selection operation can also be used to remove an index/entry pair from a table. By assigning the unevaluated table entry to its name, that element is removed from the table. This can be done by using unevaluation quotes ( ' ) or the evaln command.
t[1] := 't[1]';
t1≔t1
t[sin(x)] := evaln(t[sin(x)]);
tsin⁡x≔tsin⁡x
Getting the Number of Elements Stored in a Table
The numelems function returns the number of elements stored in a table.
numelems( table( [1] ) );
numelems( table( [1,2,3,4,5] ) );
numelems( table( [seq( i, i=1..127)] ) );
Checking If an Index Is Used
It is often useful to know if a particular index has a value in a table. Use the assigned function to check if a table index has an associated entry.
t := table( [1=1] ):
assigned( t[1] );
assigned( t[2] );
Evaluation Rules for Tables
Tables, like procedures, use last name evaluation. If a name is assigned a table, the result of evaluating that name is the name and not the table assigned to the name. For more information about last name evaluation, refer to the last_name_eval help page.
t := table([1,2,3,4]);
t≔table⁡1=1,2=2,3=3,4=4
t;
t
To get the assigned value (the table), use the eval command.
eval(t);
table⁡1=1,2=2,3=3,4=4
Extracting Data
Tables are often used as simple containers for data. Sometimes, it is useful to have a list of the indices used in the table. Maple provides the indices function for this purpose.
t := table( [a=1, b=2, c=3, d=4] );
t≔table⁡b=2,d=4,c=3,a=1
indices( t );
b,d,c,a
You may not expect to see that indices returns a sequence of lists, where each list contains the index. This is because Maple allows sequences to be used as indices in tables.
t2 := table( [ a=1, b=2, (a,b,c)=3 ] );
t2≔table⁡b=2,a=1,a,b,c=3
indices( t2 );
b,a,a,b,c
If the indices were not wrapped in a list, it would be impossible to determine if an index is a single expression or a sequence of expressions. Since using sequences as indices is uncommon, indices accepts a nolist option, for which indices returns a simple sequence and does not wrap each index in a list.
indices( t, 'nolist' );
Note that, with the nolist option, indices that are sequences are not returned properly.
indices( t2, 'nolist' );
You can also use the entries function to get all the values stored in the table.
entries( t );
2,4,3,1
entries( t, 'nolist' );
To extract the index/entry pairs as a sequence of equations, use the pairs option to either of the indices or entries commands.
entries( t, 'pairs' );
b=2,d=4,c=3,a=1
Copying Tables
If you assign a table to multiple names, all the names reference the same table. Thus, changes to the table using one name are visible from the other names.
t := table( [a=1,b=2,c=3] );
t≔table⁡b=2,c=3,a=1
t1 := eval( t );
t1≔table⁡b=2,c=3,a=1
t[d] := 4;
td≔4
eval( t );
table⁡b=2,d=4,c=3,a=1
eval( t1 );
If you want to create a copy of a table, use the copy function so that the tables can be modified independently.
t1 := copy( t );
t1≔table⁡b=2,d=4,c=3,a=1
t[e] := 5;
te≔5
table⁡e=5,b=2,d=4,c=3,a=1
Applying a Function to the Contents of a Table
The map function works with tables as it does with lists and sets. When executing a map on a table, the mapped function is given the value associated with an index. In the returned table, the result is the entry associated with the index.
t := table( [ x, x^2+2, x^3-x+1, 1/x^2 ] );
t≔table⁡1=x,2=x2+2,3=x3−x+1,4=1x2
map( diff, t, x );
table⁡1=1,2=2⁢x,3=3⁢x2−1,4=−2x3
You can use the indices and entries functions to produce a list that can be mapped over or used in a for-in loop. You can also use this technique to modify the original table.
for i in entries(t,'pairs') do t[lhs(i)] := int( rhs(i), x ); end do;
t1≔x22
t2≔13⁢x3+2⁢x
t3≔14⁢x4−12⁢x2+x
t4≔−1x
table⁡1=x22,2=13⁢x3+2⁢x,3=14⁢x4−12⁢x2+x,4=−1x
Arrays
In Maple, an Array stores data as an n-dimensional rectangular block (rtable), that is, an Array has 1 or more dimensions and each dimension has an range of integer indices. By specifying one integer from each range, an element of the Array can be indexed.
Because Arrays are mutable, the values stored in an Array can change.
Creating Arrays
To create an Array in Maple, use the Array command and specify the ranges for the dimensions. This creates a new Array with each entry initialized to 0. For Arrays, the ranges do not need to start at 1.
Array( 1..3 ); # 1 dimensional Array
Array( 1..3, 1..4 ); # 2 dimensional Array
Array( 1..3, 2..5, -1..1 ); # 3 dimensional Array
When creating an Array, you can also specify a generator function to populate the Array with data. The generator function takes an index as an argument and returns a value for the corresponding entry.
Array( 1..3, x->x+1 );
Array( 1..3, 1..4, (x,y)->(x+y) );
You can also provide the data for the Array by specifying the data as a list or nested lists.
Array( [1,2,3] );
Array( [[1,2],[3,4],[5,6]] );
Basic Data Access
Arrays are implemented in Maple as a type of rtable, a structure also used for Matrices and Vectors. This means that Arrays have two different indexing mechanisms: mathematical indexing and programmer indexing. Mathematical indexing is intended for use when the Array is viewed as a mathematical object. Programmer indexing provides functionality that is more convenient when using Arrays as a programming tool.
The basic indexing operator, [], provides mathematical indexing. Programmer indexing is accessed by using round brackets, (). For Arrays whose dimension ranges all start at 1, the two indices behave similarly.
A := Array( 1..2, 1..3 ):
A[1,1] := 1;
A1,1≔1
A(2,1) := 2;
A≔
A(1,1);
A[2,1];
You may notice that the assignment that uses programmer indexing is displayed differently than the assignment that uses mathematical indexing. This is because the result of an assignment to a programmer indexed Array is the entire array. This can be important when working with large sub-Arrays.
When the ranges do not start at 1, mathematical and programmer indexing are different. Mathematical indexing requires that the indices match the specified ranges, but programming indexing always normalizes the ranges to start at 1.
A := Array( 3..4, 5..6, (x,y)->x+y ):
A[3,5];
8
A(3,5);
Error, index out of bounds
This means that programmer indexing can always take advantage of negative indexing, which normally only works when the ranges start at 1. Negative indexing counts backwards from the end of the range.
A[3,-1];
Error, Array index out of range
A[3,6];
A(1,-1);
Sub-Array Access
A sub-Array of an Array can be accessed by specifying a subrange in place of the indices.
A := Array( 1..5, 1..5, (x,y)->x+y );
A[1..2,1..3];
A(2..4,2..3);
Sub-Array indexing can also be used to assign to the specified sub-Array.
A[2..4,2..3] := Array( [[a,a],[a,a],[a,a]] );
A2..4,2..3≔
A(4..5,4..5) := Array( [[b,b],[b,b]] );
Note that the commands perform the same operation, but display the result differently. This is the consequence of an important difference in how the modification is performed. This can be important when working with large sub-Arrays. Compare the time to perform the assignment in the following examples:
N := 4000:
A := Array( 1..N, 1..N, (x,y)->rand() ):
B := Array( 1..N, 1..N ):
t := time():
B[1001..4000,1001..4000]:=A[1..3000,1..3000]:
time()-t;
0.195
B(1001..4000,1001..4000):=A(1..3000,1..3000):
0.059
The difference in running time of these copies is due to the difference in the result of an assignment to an Array index. For mathematical indexing, a new 3000 by 3000 Array must be created as the result. With programmer indexing, the result is the Array being assigned to in its entirety - an object that already exists.
Automatic Resizing
One of the most important differences between mathematical and programmer indexing is automatic resizing. When reading from or writing to an entry using mathematical indexing, an index that is outside the bounds of the Array will raise an exception.
A := Array( [[1,2,3],[4,5,6]] );
A[1,4];
A[1,4] := a;
However, programmer indexing allows you to write to an entry that is outside the bounds of the current Array. Instead of raising an exception, the Array are automatically resized so that the element can be stored. Reading from an out-of-bounds index will still raise an exception.
A(1,4) := a;
A(3,5) := b;
More Array Indexing
There are more features of, and differences between, mathematical and programmer indexing. For more information on Array indexing, refer to the rtable_indexing help page.
Getting the Number of Elements in an Array
The numelems function returns the number of elements defined by the bounds of an Array.
numelems( Array( [1,2,3,4,5] ) );
numelems( Array( [[1,2,3],[4,5,6]] ) );
6
Getting the Bounds of an Array
As Array bounds may not start at 1, it is important that procedures that accept Arrays be aware of this possibility. The upperbound and lowerbound functions can be used to get the bounds on the ranges of an Array.
printer := proc( A ) local lower, upper, i, j; lower := lowerbound( A ); upper := upperbound( A ); for i from lower[1] to upper[1] do for j from lower[2] to upper[2] do printf( "%a ", A[i,j] ); end do; printf( "\n" ); end do; end proc:
printer( Array( [[1,2],[3,4]] ) ):
1 2 3 4
printer( Array( 2..5, 5..7, (x,y)->(x+y) ) ):
7 8 9 8 9 10 9 10 11 10 11 12
Copying an Array
As with tables, having multiple variables referencing the same Array does not create new copies of the Array. You can use copy to copy the Array.
A := Array( 1..2, 1..2 ):
B := A;
B≔
A[1,1] := 1:
B;
B := copy(A):
A[1,2] := 2:
A;
Testing If Two Arrays Are Equal
For Arrays, there are two notions of equality: do two references point to the same Array, or are they different Arrays that store the same values. To determine if two references refer to the same Array, use = and evalb. To test if two Arrays contain the same elements, use the EqualEntries command.
CompareArray := proc( A, B ) if A = B then print("two names for one array"); elif EqualEntries(A,B) then print("same elements"); else print("at least one element is different"); end if; end proc:
A := Array( [[1,2],[3,4]] );
AC := copy(A);
AC≔
CompareArray(A,AC);
same elements
AR := A;
AR≔
CompareArray(A,AR);
two names for one array
B := Array( [[1,2],[3,5]] );
CompareArray(A,B);
at least one element is different
There are some other advanced notions of equality such as whether or not arrays with undefined entries should be treated as having equal entries, and whether a Matrix and Array with identical entries should be considered the same. The IsEqual command in the ArrayTools package allows for different solutions for these two issues compared to EqualEntries. The ArrayTools package contains a variety of functions for working with Arrays. For more information, refer to the ArrayTools help page.
Applying a Function to the Contents of an Array
map can be used with an Array as you would expect
map( x->(x/2), Array( [[1,2,3],[4,5,6]] ) );
indices, entries, and the in operator work with Arrays, so you can use Arrays in add, mul, and for loops. entries(A,pairs) can also be used to obtain a list of index/value pairs in the same way that it does for tables.
A := Array( [x,x^3,sin(x)] ):
for entry in entries(A,'pairs') do A[lhs(entry)] := diff( rhs(entry), x ): end do:
Better Performance with Numeric Arrays
When creating an Array, you can specify a datatype for the Array elements. The given datatype can be either a Maple type or a hardware datatype specifier: integer[n], float[n], complex[n]. n refers to the number of bytes of data for each element. For integer[n], n can be 1, 2, 4, or 8. For float[n] or complex[n], n can be 4 or 8. The datatype integer[4] uses 4-bytes, or 32-bits per integer, and integer[8] uses 8-bytes, or 64-bits. The 64-bit version has a wider range of signed values, but uses more memory.
When assigning values into the Array, Maple will raise an exception if the given value does not match the specified type.
A := Array( [1,2,3,4], datatype=float[8] );
A[1];
1.
A[1] := 1.5;
A1≔1.5
A[2] := x^2;
Error, unable to store 'x^2' when datatype=float[8]
If you are working with numeric values that can be stored in these hardware types, it can be much faster to use an Array with a hardware type. For more information on numerical programming in Maple, see Numerical Programming in Maple.
Deprecated: array
The array data structure is an older implementation of Arrays. Its use has been deprecated; use Array instead.
4.5 Other Data Structure Operations
Filtering Data Structure Elements
The select, remove, and selectremove functions provide ways to filter the elements of data structures.
select( f, x )
remove( f, x )
selectremove( f, x )
The parameter f must be a Boolean-valued function. This function is applied to each of the elements of the data structure x. select returns the a data structure containing those elements for which f returns true. remove returns a data structure containing those elements for which f returns false. selectremove returns two structures, the first consisting of the elements for which f returned true and the second consisting of the elements for which f returns false.
The type of the return value of these functions matches the type of the argument x.
x := [seq(i,i=1..10)];
x≔1,2,3,4,5,6,7,8,9,10
select( isprime, x );
2,3,5,7
remove( isprime, x );
1,4,6,8,9,10
selectremove( isprime, x );
2,3,5,7,1,4,6,8,9,10
Calling selectremove is more efficient than calling select and remove separately.
Converting Data Structures
Maple provides the convert function, which allows various expressions to be converted from one form to another.
convert( x, t )
convert attempts to convert the expression x into the form t. In particular, Maple supports conversions between the list, set, table, and Array types.
x := [1,2,3,4];
x≔1,2,3,4
convert( x, 'set' );
1,2,3,4
convert( x, 'table' );
convert( x, 'Array' );
4.6 Other Data Structures
Records
In Maple, a record is a structured data type. It allows you to create a fixed-size structure with user-defined fields. You can use records to create customized structures that can make Maple code easier to read and write.
Create a Record
To create a new record, use the Record command. Record accepts a sequence of names as parameters. Each name becomes a field in the returned record.
r := Record( 'expression', 'variable' );
r≔Record⁡expression,variable
r:-expression := x^2;
expression≔x2
r:-variable := x;
variable≔x
int( r:-expression, r:-variable );
x33
If Record is passed a single record as an argument, a copy of that record is returned.
r2 := Record( eval(r,1) );
r2≔Record⁡expression=x2,variable=x
r2:-expression := sin(x^2);
expression≔sin⁡x2
int( r2:-expression, r2:-variable );
2⁢π⁢FresnelS⁡2⁢xπ2
Note that you must call eval on r before passing it into Record. This is because records use last name evaluation rules, similar to tables.
Record Equality
As with Arrays, two references to Records are considered equal if they reference the same structure. Two different structures that have the same fields and values are not considered equal.
r := Record( 'a'=1, 'b'=2, 'c'=3 ):
rc := r:
r2 := Record( 'a'=1, 'b'=2, 'c'=3 ):
evalb( r = rc );
evalb( r = r2 );
To compare two different records, you can use the verify command with the record argument. verify/record returns true if the two records have the same set of fields with equal values assigned to them.
r3 := Record( 'a'=1, 'b'=2, 'c'=3, 'd'=4 ):
r4 := Record( 'a'=1, 'b'=2, 'c'=4 ):
verify( r, r2, 'record' );
verify( r, r3, 'record' );
verify( r, r4, 'record' );
Packed Records
The Record constructor function can also be called with the indexed name Record[packed], to produce a packed record.
Unlike a regular record, a packed record does not create a unique instance of each field name for each record instance. When working with thousands of similar records each with many fields, this can save a significant amount of memory.
Fields of packed records do not exhibit last name evaluation. That is, the expression r:-a always produces a value, even if that value is a procedure, table, Matrix, Vector, or another record.
Similarly, it is not possible for a packed record field to not have a value. The assigned function will always return true, and unassigning a packed record field will set its value to NULL instead.
Stacks
A stack is an abstract data type that provides two main operations: push and pop. A push places a new value onto the top of the stack and pushes the existing elements down. A pop removes the element from the top of the stack, moving the elements below up. This creates a element access order referred to as last in first out (LIFO).
Stacks are useful for many operations. A typical use of a stack is to turn a recursive algorithm into an iterative one. Instead of recursing on elements, those elements get pushed onto a stack. When the current element has been handled, the element on top of the stack is removed and handled next. By using a stack, the recently discovered elements are handled before elements that were already in the stack, which is similar to how a recursive algorithm works.
Creating a Stack
In Maple, you can create a stack by calling stack:-new. If you do not specify any arguments, stack:-new creates an empty stack. Maple stacks are implemented on top of tables.
s := stack:-new():
stack:-push( 1, s );
stack:-push( 2, s );
stack:-pop( s );
You can also pass values into stack:-new that populate the stack. These elements are pushed in the order specified.
s := stack:-new(1,2,3,4,5):
Pushing and Popping
To push and pop elements onto the stack, use the stack:-push and stack:-pop functions.
stack:-push( 1, s ):
stack:-push( 2, s ):
stack:-push( 3, s ):
More Stack Functions
To get the number of elements stored in the stack, call stack:-depth.
s := stack:-new(a,b,c):
while stack:-depth( s ) > 0 do print( stack:-pop( s ) ); end do;
c
a
To test if a stack is empty, call stack:-empty.
s := stack:-new(c,b,a):
while not stack:-empty( s ) do print( stack:-pop( s ) ); end do;
You can examine the element on the top of a stack, without removing it, by calling stack:-top.
s := stack:-new(x,x^2,sin(x)):
stack:-depth(s);
stack:-top(s);
sin⁡x
stack:-pop(s);
x2
Queues
The queue is an abstract data type similar to a stack; however, instead of the most recently added element being returned first, the oldest element in the queue is returned first. Elements in a queue are analogous to people waiting in a line. The main operations provided by a queue are enqueue, which adds an element to the queue, and dequeue, which removes an element from the queue. The access order used by a queue is called first in first out, or FIFO.
A queue is used when you want to handle elements in the order that they are discovered. A typical example of using a queue is a breadth-first search of a graph. You dequeue a node and then enqueue any unvisited nodes that are neighbors of the current node. By using a queue, the order in which the nodes are visited is breadth-first.
Create a Queue
To create a queue in Maple, use the queue:-new command.
q := queue:-new():
queue:-enqueue( q, 1 );
queue:-enqueue( q, 2 );
queue:-dequeue( q );
You can also pass values into queue:-new to populate the new queue. The elements are enqueued in the order they are specified.
q := queue:-new( 1,2,3 ):
Enqueue and Dequeue
You can insert a new element into a queue using queue:-enqueue and remove an element from the queue using queue:-dequeue.
queue:-enqueue( q, 1 ):
queue:-enqueue( q, 2 ):
queue:-enqueue( q, 3 ):
More Queue Functions
You can get the number of elements stored in the queue by calling queue:-length.
q := queue:-new(a,b,c):
while queue:-length( q ) > 0 do print( queue:-dequeue( q ) ); end do;
You can test if a queue is empty by calling queue:-empty.
q := queue:-new(c,b,a):
while not queue:-empty( q ) do print( queue:-dequeue( q ) ); end do;
You can examine the front element of a queue, without removing it, by calling queue:-front.
q := queue:-new(x,x^2,sin(x)):
queue:-length(q);
queue:-front(q);
queue:-dequeue(q);
4.7 Data Coercion
Data Coercion refers to the ability to take one data type and automatically convert it into a different data type. This is particularly useful for arguments passed into a procedure, where the expected data type for the procedure is explicitly declared. For more information on data coercion in Maple, see the coercion help page.
Maple provides two methods for enabling data coercion. For more information see The coercion Modifiers.
4.8 Data Structure Performance Comparisons
Maple provides many different data structures, many of which can be used together to perform specific tasks. However, the different performance characteristics of the data structures means that some are better than others in certain situations.
Indexing
The time to perform an indexed look-up into a list, set, table, and Array are all constant time operations. This means that the time needed to find the element does not vary based on the number of elements stored in the structure. Time to perform a look-up into a list or set is relatively similar and is faster than Arrays, which is faster than a table.
Similarly, writing into a table or Array is also a constant time operation, with Array look-ups being slightly faster than table look-ups.
Membership
The member function determines if a particular element is stored in a structure. For lists, this requires a linear search of the data in the list. Therefore, the time is proportional to the total length of the list. A set is sorted, so searches of the list can be performed more quickly. Searching within a set takes time proportional to the log[2] of the number of elements in the set.
You can use a table for very fast membership testing. Use the table key as objects you want to test for, and anything you want for the value. You can then call the assigned command to test if the element exists in the table. A table index is a constant time operation, so this membership test is also constant time.
N := 2*10^5:
memtest := proc( D, N ) local i; for i from 1 to N do member( i, D ): end do: end proc:
L := [seq( i, i=1..N )]:
time(memtest(L,N));
26.625
S := {seq( i, i=1..N )}:
time(memtest(S,N));
0.352
t := table( [seq( i=1, i=1..N ) ] ):
start := time(): for i from 1 to N do assigned( t[i] ): end do: time()-start;
0.200
Note that to benchmark the list and set membership functions, the call to member is within a function. This is because of the Maple evaluation rules. If the call to the member command is at the top level, the list or set is fully evaluated, which requires inspecting each element of the list or set for each call to member. The overhead required for these full evaluations would distort the results.
For more information on the Maple evaluation rules, see Unevaluated Expressions.
Building a Collection of Data
It is often necessary to build a collection of data when you do not know how many elements you are going to have. You should use a table, Array (using programmer indexing), stack, or queue. All of these mutable structures support adding elements in constant time. Using an immutable data structure is slower; the use of a list or a set is not recommended in this situation.
N := 5*10^4:
A := Array( [] ): start := time(): for i from 1 to N do A( i ) := 1: end do: time()-start;
0.048
t := table(): start:=time(): for i from 1 to N do t[i] := 1: end do: time()-start;
0.092
l := []: # using a list is quite slow start := time(): for i from 1 to N do l := [ op(l), i ]: end do: time()-start;
47.802
4.9 Avoiding Common Problems
When working with data structures, there are a few common problems that you may encounter. This section describes some of these problems to help you avoid making these mistakes yourself.
Passing Sequences into Functions
When a sequence is passed into a procedure, each element of the sequence is treated as a separate argument. This can lead to errors if the procedure is unable to handle the multiple arguments, for example, with the op command.
s := a,b,c;
s≔a,b,c
op( 2, s );
Error, invalid input: op expects 1 or 2 arguments, but received 4
Instead, wrap the sequence in a list.
op( 2, [s] );
Incorrect Index Values
Be careful with the values used for indexing. Specifying values outside valid ranges will raise exceptions. In particular, in Maple, lists and sets start indexing at 1, not 0.
L := [1,2,3,4,5,6,7,8];
L≔1,2,3,4,5,6,7,8
L[0];
Error, invalid subscript selector
L[9];
Further, when specifying the endpoints of a range, make sure that the left-hand side of the range specifies an element before the element specified by the right-hand side.
L[6..3];
L[6..-5];
The only exception to this is if the left-hand side of the range is n, then the right-hand side can be n-1 and the result of this range is an empty structure (list or set).
L[6..5];
Similar exceptions happen with using [] for selection from Arrays.
A := Array( [5,6,7,8,9,10] );
A[7];
A[5..3];
Error, inverted range in Array index
Another type of index error occurs when sum is used instead of add to obtain explicit sums over all the elements of a list, Array, Matrix, Vector, or similar data structures.
V := Vector(5,{(1)=1,(2)=2,(3)=3,(4)=4,(5)=5}):
sum(V(n),n=1..5);
Error, unsupported type of index, n
Array Indices Do Not Always Start at 1
In an Array, the lower bound of the indices may not be 1. If you write a procedure that accepts an Array, you should be prepared to handle Arrays that have been defined for a range of indices that does not start at 1. For more information on how to write procedures that can handle such Arrays, see Getting the Bounds of an Array.
Do Not Treat Lists and Sets as Mutable
You can use commands such as op and subsop with lists and sets to create new structures. It is, therefore, possible to treat lists and sets like mutable structures. However, by doing so, you can add a significant amount of processing time to your computations. Make sure that you use actual mutable structures instead.
N := 2*10^4:
l := [seq( i=i, i=1..N)]:
t := table( l ): start:=time(): for i from N to 1 by -1 do t[i] := evaln(t[i]): end do: time()-start;
0.032
start := time(): for i from N to 1 by -1 do l := subsop( i=NULL, l ); end do: time()-start;
20.277
4.10 Exercises
Define a set with elements that are the powers of 13 modulo 100 for exponents ranging from 1 to 1000. Is 5 a member of the set? Why is it beneficial to use a set instead of a list?
Hint: You can determine the set by using one statement if you use the seq command.
Generate the sums of 4 and the first 100 multiples of 3. Determine the sums that are square-free composite numbers.
Hint: The NumberTheory package has a function that you need to use.
Find floating-point approximations for the sum of the square root and cubic root of each of the first 15 powers of 2.
Hint: Use map, seq, and zip.
Write a procedure that implements the sieve of Eratosthenes: Count the number of integers (less than or equal to a given integer) that are prime.
Download Help Document