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:
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:
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.
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.
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.