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
Str2Array(cStr,@aArray)
and
Array2Str(@aArray)
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]
		endif
	endfor
endfor	

&& 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]
	endif
endfor
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
Str2Array(@aArray,cStr)

or
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
endif	

&& 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)
	enddo
	nPos = 1
	&& determine number of rows
	nCRPos = len(cStr)
	do while nPos != 0
		mRows = mRows + 1
		nPos = at(chr(13),cStr,mRows)
	enddo
	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.
					else
						bArray[m.i,m.j] = ""
					endif			
				else
					bArray[m.i,m.j] = ;
							substr(cSubStr,nPos1,mCommaPos -nPos1)
				endif
				nPos1 = mCommaPos + 1		
			endfor
			nPos = nCRPos + 2
			nPos1 = 1
			lEmpty = .f.
		endfor	
	endif	
else
	&& Probably a single line entry with no CR
	if len(cStr) > 0
		Dimension bArray[1]
		bArray[1] = alltrim(cStr)
	endif	
endif		
ENDFUNC		

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
endif	
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"
				else
					mStr = mStr + "FALSE"	
				endif
		ENDCASE		
		if m.j != mCols
			mStr = mStr + ","
		endif
	endfor
	mStr = mStr + CRLF		
endfor	
RETURN mStr
ENDFUNC

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("Whatever",1)
oParams.AddProperty("WhateverElse","My Value")
oParams.AddProperty("AnotherParam",.T.)
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
?oResult.ResultTwo
?oResult.ResultThree
?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)


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

APPEND FROM ARRAY array__Values
						
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 )