Language Elements and Reference:xpplrm

Operations using arrays Foundation

Using the "array" data type, several values can be referenced at once in a single variable. An array contains values in the array elements and a variable with "array" data type contains a reference to an array. An array is useful when lists of related values are to be accessed using a single memory variable. The operators that can be applied to the "array" data type are listed in the following table:

Operators for arrays
Operator Description
{} Delimiter for literal array
[] Array element operator
== Tests whether two arrays are identical
= Comparison: equal
!= <> # Comparison: not equal
= Assignment
:= Inline assignment

The two most important operators {} and [] are not classified only as operators. They are used in creating arrays which can be done within the variable declarations.

PROCEDURE Main 
   LOCAL aArrayA := {"A", "B", "C"} 
   LOCAL aArrayB[3] 

   ? aArrayA[3]                       // result: C 
   ? aArrayB[3]                       // result: NIL 

   aArrayB[1] := 100 
   aArrayB[2] := 200 
   aArrayB[3] := 300 

   ? aArrayB[3]                       // result: 300 

RETURN 

Using the inline assignment operator and the curly braces, literal arrays can be created containing values in each array element. This can occur within the declaration of lexical variables (LOCAL and STATIC) or in the creation of dynamic memory variables (PRIVATE and PUBLIC). When used as part of the variable declaration, the array element operator [] is used to define the number of elements in the array referenced by the variable. When defined in this way, each array element initially contains the value NIL. A numeric index enclosed in brackets, is used to access and initialize each individual array element.

Multi-dimensional arrays

In each of its elements, an array can contain a value of any data type, including another array. In this way, nested or multi-dimensional arrays are supported. Multi-dimensional arrays can be dimensioned within the variable declaration and can be assigned as a literal. Examples of a two dimensional array and a three dimensional array appear in the following code:

** Dimension Array in LOCAL statement ************** 
   LOCAL aArray2Dim[5,2]               // two dimensional 
   LOCAL aArray3Dim[2,3,2]             // three dimensional 

** Literal multi-dimensional arrays    ** two dimensional 
   aArray2Dim := { ;                   // open first dimension 
                   {11, 12}, ;         // second dimension 
                   {21, 22}, ; 
                   {31, 32}, ; 
                   {41, 42}, ; 
                   {51, 52}  ; 
                 }                     // close first dimension 

                                       ** Three dimensional 
   aArray3Dim := { ;                   // open first dimension 
                   { ;                 // open second dimension [1] 
                     {111, 112}, ;     // third dimension 
                     {121, 122}, ; 
                     {131, 132}  ; 
                   },;                 // close second dimension 
                   { ;                 // open second dimension [2] 
                     {211, 212}, ;     // third dimension 
                     {221, 222}, ; 
                     {231, 232}  ; 
                   } ;                 // close second dimension 
                 }                     // close first dimension 

** Access to multi-dimensional arrays 
   ? aArray2Dim[4,2]                   // result 42 
   ? aArray3Dim[1,3,2]                 // result: 132 

   ? aArray2Dim[5][1]                  // result: 51 
   ? aArray3Dim[2][2][1]               // result: 221 

Using the array element operator, access to array elements of higher dimensions requires specifying the numeric indexes for each dimension of the array. Two different notations can be used: the indexes can be specified as a comma separated list or each index can be separately enclosed in brackets.

Comparison of arrays

Variables contain only references to arrays. Comparisons of arrays, therefore, occur on the basis of the references for variables of the "array" data type. When comparing two arrays, the exact equals operator == must be used. If the two variables contain the same reference, the exact equals operator returns the value .T. (true). In this case, the values of both variables are identical. An array can be compared using the simple equals operator and the "not equal" operator only when the value NIL is the second operand.

aArray1 := { 1,.T., "Xbase++" }     // assign array reference 
aArray2 := { 1,.T., "Xbase++" }     // new array reference 
aArray3 := aArray1 

? aArray1 == aArray2                // result: .F. 
? aArray1 == aArray3                // result: .T. 

? aArray1 == NIL                    // result: .F. 

? aArray1 = NIL                     // result: .F. 
? aArray1 <> NIL                    // result: .T. 

In the example, two arrays with array elements containing identical values are assigned to two variables. A comparison of the two variables aArray1 and aArray2 returns the value .F. (false). Even though the contents of both arrays are identical, the two variables contain different array references. The comparison of aArray1 and aArray3 returns the value .T. (true) because these two variables contain identical references to the same array.

To test whether two arrays contain the same values in their elements, the values of each of the elements must be compared with one another. A user-defined function is required for this. An example of such a function is found with the examples for array functions.

Passing by reference

When an array is passed to a procedure or function, the value of the passed variable is the reference to the array. This means that array elements can be changed within subroutines and these changes affect the array within the calling routine. The difference between passing an array reference and passing the variable by reference using the reference operator is shown below:

************** 
PROCEDURE Main 
   LOCAL aChar := {"A", "B"} 

   ? aChar[1], aChar[2]                // result: A B 

   Proc1( aChar )                      // passes array reference 
   ? aChar[1], aChar[2]                // result: i j 
                                       // passes variable using 
   Proc1( @aChar )                     // the reference operator 
   ? aChar[1], aChar[2]                // result: Y Z 

RETURN 

************************* 
PROCEDURE Proc1( aArray ) 

   aArray[1] := "i"                    // change array 
   aArray[2] := "j"                    // elements 
   aArray := {"Y", "Z"}                // change array reference 

RETURN 

In the example above, after the first call to the procedure Proc1()the elements of the array referenced by the variable aChar are changed. In the second call, aChar is passed by reference and is assigned to a new array reference in Proc1(). The original array is no longer referenced by any variable and is removed from memory by the garbage collector.

Functions for arrays

Xbase++ provides many functions in its runtime library for managing arrays. These perform operations like "search", "sort", "copy" and handle dynamic changes in the number of array elements at runtime of a program. The following table is an overview of all array functions:

Functions for arrays
Function Description
AAdd() Enlarges array by one element
AClone() Copies multi-dimensional array
ACopy() Copies one dimensional array
ADel() Deletes array element without changing length of array
AEval() Executes code block for some or all array elements
AFill() Fills array with values
AIns() Inserts array element without changing length of array
Array() Creates array
AScan() Searches in a one dimensional array
ASize() Changes size of the first dimension of an array
ASort() Sorts array
ATail() Returns the value in the last element of an array
Empty() Tests whether an array has no elements
Len() Returns the number of elements in the first dimension of an array
Type() Returns the data type via macro operator
Valtype() Returns the data type

Short examples for some of the array functions showing basic array operations are shown in the following sections.

Create and fill array

In addition to being able to create an array within the variable declaration, an array of any dimension containing NIL in all elements can be assigned to a variable using the function Array():

aArray1Dim := Array(5)              // one dimensional 
aArray2Dim := Array(5,2)            // two dimensional 
aArray3Dim := Array(2,3,2)          // three dimensional 

The function AFill() fills a one dimensional array with a constant value. The following two lines result in identical arrays:

aArray1 := { 0, 0, 0, 0, 0 } 
aArray2 := AFill( Array(5), 0 ) 

Determine the number of elements in an array

The function Len() determines the number of elements in the first dimension of an array. The function Empty() can be used to test whether the array has any elements at all:

LOCAL aArray[2,5,3] 

   ? Len(aArray)                    // result: 2 
   ? Len(aArray[1])                 // result: 5 
   ? Len(aArray[1,1])               // result: 3 

   ? Empty(aArray)                  // result: .F. 

   aArray := {} 

   ? Empty(aArray)                  // result: .T. 
   ? Len(aArray)                    // result: 0 

   aArray := {NIL} 

   ? Empty(aArray)                  // result: .F. 
   ? Len(aArray)                    // result: 1 

The function Empty() tests the number of elements in an array and returns the value .T. (true) only when the array has no elements. When an array contains an element having the value NIL, it is not an empty array but has an element with the value NIL.

Change array dynamically

Some of the most frequent array operations are the enlarging and shrinking of arrays allowing the insertion and deletion of individual elements. The two functions AAdd() and ASize() change the size of an array (the number of its elements) and the functions AIns() and ADel() insert or delete an array element without changing the total number of elements:

LOCAL aArray:={}                    // empty array 

AAdd( aArray, "A" )                 // array is: {"A"} 
AAdd( aArray, "B" )                 // array is: {"A","B"} 
AAdd( aArray, "C", 1 )              // array is: {"C","A","B"} 

ASize( aArray, 4 )                  // array is: {"C","A","B",NIL} 

ADel( aArray, 1 )                   // array is: {"A","B",NIL,NIL} 

AIns( aArray, 2 )                   // array is: {"A",NIL,"B",NIL} 
aArray[2] := .T.                    // array is: {"A",.T.,"B",NIL} 

AIns( aArray, 3, .F. )              // array is: {"A",.T.,.F.,"B"} 

ASize( aArray, 2 )                  // array is: {"A",.T.} 

AAdd() is used to attach a value to the end of an array (the default) or at a specified position in the array. The function ASize() enlarges or reduces an array to the specified number of elements. If the array is enlarged, the new elements contain the value NIL. If an array is reduced in size, the values of the elements at the end of the array are lost and are removed from memory by the garbage collector.

The function ADel() deletes an element at the specified position in the array. All subsequent elements move one position up and the last array element has the value NIL. AIns() does the opposite: all elements starting at the specified position are pushed down by one element toward the end of the array and the value in the last element is lost.

Copy array

Xbase++ provides two functions for copying arrays: ACopy() copies the values from the elements of one array to a second array and AClone() duplicates an array. The function ACopy() copies only the values in the first dimension of the array and does not handle multi-dimensional arrays. When multi-dimensional arrays are copied using ACopy() both the source array and the target array contain the same references to the subarrays:

LOCAL aSource := { 1 , 2 , 3 } 
LOCAL aTarget := {"A","B","C"} 

ACopy( aSource, aTarget )           // aTarget is: {1, 2, 3} 

aSource[1] := {4, 5}                // creates subarray 
? aSource[1,1]                      // result: 4 
? aSource[1,2]                      // result: 5 

ACopy( aSource, aTarget )           // aTarget is: {{4,5}, 2, 3} 

aTarget[1,1] := 0                   // aTarget and aSource have 
aTarget[1,2] := 1                   // the same subarray 
? aSource[1,1]                      // result: 0 
? aSource[1,2]                      // result: 1 

aTarget := AClone(aSource)          // duplicates array 

aTarget[1,1] := 4                   // aTarget and aSource have 
aTarget[1,2] := 5                   // different subarrays 
? aSource[1,1]                      // result: 0 
? aSource[1,2]                      // result: 1 
? aTarget[1,1]                      // result: 4 
? aTarget[1,2]                      // result: 5 

When values from a multi-dimensional array are copied to a second array, a duplicate of the source array should generally be created using the function AClone(). Otherwise, two different arrays contain references to the same subarrays and changes in either array lead to simultaneous changes in the other array. This can be desirable in some situations, but can also lead to very serious programming errors.

Cyclical arrays cannot be duplicated using AClone(). A cyclical array exists when the reference to the array is contained in an element of the same array. In this case a runtime error occurs, since AClone() would otherwise be caught in infinite recursion. Example:

LOCAL aSource := {1, NIL} , aTarget 

aSource[2] := aSource                  // cyclical array 

aTarget := AClone( aSource )           // runtime error 

Sort array

The function ASort() sorts a one dimensional array in ascending order:

LOCAL aArray := {2,1,4,3} 

   ASort(aArray)                    // aArray is: {1,2,3,4} 

When an array is to be sorted in descending order or when a multi-dimensional array is to be sorted, a code block defining the sort condition must be specified when calling the function. See the next chapter "Operations using Code Blocks".

Search an array

The function AScan() searches an array for an element matching a specific value.

LOCAL aArray := {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"} 

   ? AScan(aArray, "Wed")            // result: 4 
   ? AScan(aArray, "Sat")            // result: 7 

Similar to ASort(), the simplest call to AScan() searches only a one dimensional array. Searching a multi-dimensional array requires that a code block containing the search condition be specified. Code blocks are described in the next chapter.

Process array iteratively

The most frequent operations with arrays involve iterative processing of some or all elements in an array. For this purpose loops are often used, generally a FOR...NEXT loop programmed as follows:

FOR i:=1 TO Len(aArray) 
   <Program code with aArray[i]> 
NEXT 

In this example, the counter variable of the loop i can be used as a pointer to an array element. The loop continues until the last element of the array is reached. The last element is determined using the function Len(), since this function returns the number of elements in an array.

When processing an array within a FOR..NEXT loop, it is recommended that the end of the loop be determined using the value of a variable and not directly using the function Len(). The following lines demonstrate the difference:

LOCAL aArray[5000], i 

FOR i:=1 TO Len(aArray)           // end of the loop is 
    aArray[i] := i                // determined using Len() 
NEXT 

nLen := Len(aArray) 
FOR i:=1 TO nLen                  // end of the loop is 
    aArray[i] := i                // determined using a variable 
NEXT 

The example shows two valid FOR..NEXT loops which assign the consecutive numbers from 1 to 5000 to the elements of an array. In the first loop, the function Len() is called 5000 times, because the end of loop is determined using this function. Since the size of the array is not changed within the loop, it is better to determine the number of elements only once and store the value in a variable. The variable is used as the end value for the FOR...NEXT loop. The second loop in the example runs about 15% faster than the first loop, and is thus a valid optimization technique.

Processing an array within a DO WHILE loop requires using the increment operator to increment a counter variable used to access the next array element each time the loop runs. The function ACompare() (shown below) provides an example of this process. The user-defined function ACompare() tests whether the contents of two arrays are identical regardless of the number of dimensions. The function is called recursively for multi-dimensional arrays in order to compare the elements in the higher dimensions.

FUNCTION ACompare(aArray1, aArray2) 
   LOCAL nElements := Len(aArray1)       // number of elements 
   LOCAL lEqual    := (nElements == Len(aArray2)) 
   LOCAL nCount    := 0 
   LOCAL cType 

   DO WHILE lEqual .AND. ++nCount <= nElements 

      cType := Valtype( aArray1[nCount] ) // determine data type 
      lEqual:= (cType == Valtype( aArray2[nCount] )) 

      IF lEqual 
         IF cType == "A"                  // compare subarrays 
            lEqual := ACompare(aArray1[nCount], aArray2[nCount]) 
         ELSE 
            lEqual := (aArray1[nCount] == aArray2[nCount]) 
         ENDIF 
      ENDIF 

   ENDDO 

RETURN lEqual 

Iterative processing of arrays can also be performed using the function AEval(). This requires the use of a code block and is described in the next chapter.

Store an array in a file

The command SAVE can be used to save arrays in XPF files. These files save memory variables. In order to be stored, an array reference must be contained in a dynamic memory variable (a PRIVATE or PUBLIC variable). The values of lexical variables cannot be written to a file using SAVE. The command RESTORE reloads an array from an XPF file into memory.

PRIVATE aArray := {"Xbase++",.T.,1}    // dynamic memory variable 

SAVE TO TestFile ALL LIKE aArray       // save aArray in file 
aArray := NIL                          // assign NIL 
RESTORE FROM TestFile                  // aArray is again 
                                       // {"Xbase++",.T.,1} 

The ability to save arrays in field variables depends on the available DatabaseEngine. The DBFNTX DatabaseEngine does not allow arrays to be stored in field variables.

Feedback

If you see anything in the documentation that is not correct, does not match your experience with the particular feature or requires further clarification, please use this form to report a documentation issue.