Wiki Home

VFP Array Discussion

Namespace: VFP
Array Manipulation
There are some helpful routines that manipulate two dimensional arrays to do both matrix math and more advanced array functionality than what VFP offers. Click here Matrix Math.

VFP arrays - Trick or Treat.
A discussion trying to learn more details about VFP arrays

Ever since I first starting with Clipper, I have been fascinated with the power and versatility of arrays. You can transfer a lot of information in arrays to and from and while I feel confident with array manipulation, I still want to know more about what VFP does with arrays 'under the hood'.

Here are some of the questions I have and would like answered. After these questions I will present 2 functions to the public domain called
which can be used to pass lots of data between VFP forms (which, I feel, has always been a small limitation of VFP forms).

If my assumptions are totally wrong, please don't hose me too badly.

Let's dimension an array for discussion. The array represents customer information (pretty famous customer list!)
Dimension laCust[3,4]

laCust[1,1] = 10001
laCust[1,2] = "John"
laCust[1,3] = "Howard"
laCust[1,4] = .t.

laCust[2,1] = 10003
laCust[2,2] = "George"
laCust[2,3] = "Bush"
laCust[2,4] = .t.

laCust[3,1] = 10004
laCust[3,2] = "Tony"
laCust[3,3] = "Blair"
laCust[3,4] = .t.

We can see that there are 12 elements in this array. One of the first things that I noticed with arrays was that you could redimension arrays (at will) for any sort of processing. The following example loops through an array looking for a logical element and when found, reverses the value using the NOT operator (!). If you cut and paste the following snippet to a .PRG, and run it with the debugger, you can follow what happens. Although the example is trivial, it demonstrates the concept. Some array manipulation can get pretty complicated.
local i, x
for m.i = 1 to 3
	for m.x = 1 to 4
		if vartype(laCust[m.i,m.x]) = "L"
			laCust[m.i,m.x] = ! laCust[m.i,m.x]

&& The following code works just as well
Dimension laCust[12]  && the product of  3 and 4
for m.i = 1 to 12
	if vartype(laCust[m.i]) = "L"
		laCust[m.i] = ! laCust[m.i]
Dimension laCust[3,4]   && revert to the original dimensions.
m.i = 1   && Line added so that debugger doesn't close early

There is no need to redimension the array because indexing a two dimensional array with a single subscript is allowed -- it indexes the element number. eg. laCust[1,1] = laCust[1], laCust[1,2] = laCust[2], ..., laCust[3,4] = laCust[12] - Albert Ballinger
I think that you will agree that there is less room for error in the second approach and it is clearer to follow the logic.

That depends. Switching from a 2-D array to a linear array may confuse the next programmer who reads the code. Its obvious from the first example that you are traversing all columns and all rows. -- Mike Yearwood

Discussion 1
Given that arrays in VFP can be of any type and any length, AND, given that VFP is written in Visual C++, AND that VC++ has no such loose storage formats, what's happening behind the scenes?
C has "struct" and "union" for this purpose and C++ adds "class". - Albert Ballinger
Hypothesis 1.
The VFP engine stores an array of pointers, one for each element in the VFP array. This would account for the variable lenghts and types.

Hypothesis 2.
The VFP engine converts all elements into strings and stores them in an array of bytes

Hypothesis 3. -- Only those privy to the VFP source truly know, but...
Since each element in the array can store any type variable (except for array) in any element of the array, then we can safely assume that the array elements are Variant Variables. (What exactly a Variant Variable is, Who Knows? Probably a structure/object that stores the type and a pointer to the data. If a Structure, any assignment to it must go through an assignment function that allocates the proper amount of memory. An object could automatically care for this). Second, since the array can be re-defined, it mustn't be a native C structure or Array, but instead either a block of memory allocated then addressed as an array of Variant, or perhaps something higher-level, like a linked list of variants (which would be slower to access). The former would be simple and quick, and would give ready access to any element of the array, and that element could be any type, making it, in effect, a variable-length array of pointers. - wgcs
Look in the VFP help index for "value structure" for the likely structure. - Albert Ballinger
Hypothesis 4.
(Your comments welcome here)

Discussion 2
What is the value of the aElement() function given the above example.

Here are 2 functions for passing information between forms and back. These functions are quite old and could do with some updating (like the above example), but they work for me, so I haven't bothered.

Also, these 2 functions work well for storing array information into memo fields and then restoring arrays from the memo field. I use these 2 functions for storing report details/variables/user preferences into memo fields.

See the SAVE TO and RESTORE FROM commands in the VFP help for a much faster way to push/pop arrays. -- Mike Yearwood

The strings of the array elements end up being comma separated, you can use any separator, just change the code.

The way they would be employed would be
Do Form1 with Array2Str(@aArray)  && notice the pass by reference

If you are returning multiple values from a form ( remember to set it as MODAL), In the UNLOAD event, assemble the string of the array elements and use (eg) RETURN cStr, so the call to the form would look like
Do Form1 with Array2Str(@aArray) TO cStr

replace SomeTable.cMemo with Array2Str(@aArray)

# DEFINE CRLF chr(13) + chr(10)

Function Str2Array(aArray,cStr)
local nPos,mCols,nCRPos,mRows,i,j,mCommaPos,nPos1,nLen,lEmpty
lEmpty = .f.
nPos = 1
mCols = 0
mRows = 0
&& add CRLF to end if doesn't exist
cStr = alltrim(cStr)
nLen = len(cStr)
if substr(cStr,nlen-2,1) != chr(13) && NO CRLF at end
	cStr = cStr + CRLF

&& determine number of columns
nCRPos = at(chr(13),cStr)
if nCRPos > 0
	do while nPos != 0 AND nPos <= nCRPos
		mCols = mCols + 1
		nPos = at(",",cStr,mCols)
	nPos = 1
	&& determine number of rows
	nCRPos = len(cStr)
	do while nPos != 0
		mRows = mRows + 1
		nPos = at(chr(13),cStr,mRows)
	mRows = mRows -1 && empty entry at end
	nPos = 1
	nPos1 = 1
	if mCols > 0 AND mRows > 0
		Dimension bArray[mRows,mCols]
		for m.i = 1 to mRows
			nCRPos = at(chr(13),cStr,m.i)
			cSubStr = substr(cStr,nPos,nCRPos - nPos)
			for m.j = 1 to mCols
				mCommaPos = at(",",cSubStr,m.j)
				if mCommaPos = 0
					&&Last entry on line
					if !lEmpty
						bArray[m.i,m.j] = ;
							substr(cSubStr,nPos1,len(cSubStr) - nPos1 + 1)
						lEmpty = .t.
						bArray[m.i,m.j] = ""
					bArray[m.i,m.j] = ;
							substr(cSubStr,nPos1,mCommaPos -nPos1)
				nPos1 = mCommaPos + 1		
			nPos = nCRPos + 2
			nPos1 = 1
			lEmpty = .f.
	&& Probably a single line entry with no CR
	if len(cStr) > 0
		Dimension bArray[1]
		bArray[1] = alltrim(cStr)

FUNCTION Array2Str(aArray)
local mStr,mLen,i,mCols,j
mStr = ""
mLen = alen(aArray,1)
mCols = alen(aArray,2)
if mCols = 0
	Dimension aArray[mLen,1]
	mCols = 1
for m.i = 1 to mLen
	for m.j = 1 to mCols
		Do Case
			Case vartype(aArray[m.i,m.j]) = "C"
				mStr = mStr + aArray[m.i,m.j]
				*mStr = mStr + alltrim(aArray[m.i,m.j])
			Case vartype(aArray[m.i,m.j]) = "N"
				mStr = mStr + alltrim(str(aArray[m.i,m.j],12,3))

			Case vartype(aArray[m.i,m.j]) = "D"
				mStr = mStr + dtoc(aArray[m.i,m.j])
			Case vartype(aArray[m.i,m.j]) = "T"
				mStr = mStr + ttoc(aArray[m.i,m.j])
			Case vartype(aArray[m.i,m.j]) = "L"
				if aArray[m.i,m.j] = .t.
					mStr = mStr + "TRUE"
					mStr = mStr + "FALSE"	
		if m.j != mCols
			mStr = mStr + ","
	mStr = mStr + CRLF		

Peter Easson
If the primary interest in these functions is to pass multiple results from a form, here's a method that requires no utility functions, and is self documenting:
oParams = CREATE("CUSTOM")
oParams.AddProperty("WhateverElse","My Value")
DO FORM MyForm WITH oParams TO oResult
?oResult.ResultOne   && These property names are created in "MyForm" and are defined as the results of the form
?oParams.Whatever  && This could have a new changed value
- wgcs
I agree that the use of an object is much more readable/maintainable than an array. However, an object and its properties are not amenable to processing using for each/endfor or for/enfor loops as arrays are. The optimal solution is, of course, a synthesis of the two. COLLECTIONS. Collections are array properties of objects. The objects have inherent methods for manipulating the collection properties, similar to for/endfor loops. - Ray Kirk
Note, however, that the object is capable of having array properties, so passing objects has all the benefits of passing arrays. -- wgcs
Hmmm - for that purpose, why not use something a shade more persistent,
and even more self-documenting, such as:

*	pass_a_set.prg

LOCAL m.count__Rows, m.count__Columns

STORE 10 TO m.count__Rows, m.count__Columns
LOCAL ARRAY array__Values[ m.count__Rows, m.count__Columns ]

STORE "Value 01,01" TO array__Values[ 01, 01 ]
STORE "Value 01,02" TO array__Values[ 01, 02 ]
*	...
*	(or populate it in whatever way)

  cursor__Values__Original ( ;
    thing01 C(10), ;
    thing02 M, ;
    thing03 N(10,2) ;

DO FORM form__Pass_a_Set	;
  WITH "cursor__Values__Original" ;
  TO m.alias__cursor__Values__Final

*  when done:
USE IN cursor__Values__Original
USE IN (m.alias__cursor__Values__Final)

- this also allows SCAN/ENDSCAN, use of filters, indexes & queries, etc.

But this doesn't work with forms with private datasessions. --Kurt

Category Code Samples Category VFP Commands Matrix Math
( Topic last updated: 2006.02.27 01:51:42 PM )