April 26, 2024, 12:52:45 PM

News:

Own IWBasic 2.x ? -----> Get your free upgrade to 3.x now.........


2D array sub parameters ?

Started by danbaron, July 26, 2009, 03:05:03 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

danbaron

July 26, 2009, 03:05:03 AM Last Edit: July 26, 2009, 03:43:47 AM by danbaron
Hi.

This is my experience of woe.

I was trying to pass a two dimensional square array to a subroutine using EB.

It didn't work right, so I made a little console program (below), which demonstrates the trouble.

I defined a constant, "D", with the dimensions of the array.

CONST D = 3

Then, I tried to write the heading of the subroutine, like this,

SUB PRINTARRAY(M[D,D] AS INT)

For me, doing that doesn't work.

It compiles and runs, but inside the subroutine, I think the array values are wrong.

To make it work properly, I had to explicitly put the array dimensions in the subroutine heading, like this,

SUB PRINTARRAY(M[3,3] AS INT)

Doing it explicitly, is harder, especially if you have many subroutines which take array parameters.
It would be easier to just change one or more constants, at the top of the file.

The program output I get using SUB PRINTARRAY(M[D,D] AS INT), is

1
2
3
4
5
6
7
8
9

1
1
1
0
0
0
0
0
0

The array is printed twice, first from the main program, and then from the subroutine.
I think, the subroutine printing, is wrong; i.e., the array should not have changed.

When, I instead use SUB PRINTARRAY(M[3,3] AS INT), the output seems to me to be correct,

1
2
3
4
5
6
7
8
9

1
2
3
4
5
6
7
8
9

You can see in the code, that I also tried using "$DEFINE D 3", instead of, "CONST D = 3".
But, I still got the same result as for "CONST D = 3".

My guess is that you cannot use constants for the dimensions of a two dimensional array,
in the heading of a subroutine. It seems, the compiler doesn't look in subroutine headings,
to do constant substitutions. I could understand the confusion, because,
in other cases, subroutine parameters have arbitrary names.

But, I could be wrong, and just be messing something up.

Dan.


CONST D = 3
'$DEFINE D 3

DEF A[D,D]:INT
INT I,J
STRING S

FOR I = 0 TO D-1
FOR J = 0 TO D-1
A[I,J] = I*D + J+1
PRINT A[I,J]
NEXT J
NEXT I
PRINT
PRINTARRAY(A)
INPUT S
END

SUB PRINTARRAY(M[D,D] AS INT)
INT I,J
FOR I = 0 TO D-1
FOR J = 0 TO D-1
PRINT M[I,J]
NEXT J
NEXT I
ENDSUB

"You can't cheat an honest man. Never give a sucker an even break, or smarten up a chump."  -  W.C. Fields

billhsln

You are working too hard.

Define the array INT M[3,3] or INT M[20,20], in the main program, in the SUB PRINTARRAY().  As long as PRINTARRAY is in the same $Main program, EBasic will allow you to use the variable, just do NOT define it in the SUB.


CONST D = 3
INT M[D,D],X
PRINTARRAY()
INPUT X
END

SUB PRINTARRAY()
FOR I = 0 TO D-1
FOR J = 0 TO D-1
M[I,J] = I*D + J+1
PRINT M[I,J]
NEXT J
NEXT I
RETURN
ENDSUB


The above code runs exactly as written.

Bill
When all else fails, get a bigger hammer.

danbaron

July 26, 2009, 05:12:21 PM #2 Last Edit: July 26, 2009, 05:16:39 PM by danbaron
Hi Bill.

I agree, your way works.

But, one of the reasons that subroutines and functions take parameters,
is so they can be generalized to work with variations of a particular type of data.

Say I have two arrays, which I want to process in the same way, in a particular program.

INT A[10, 10]
INT B[20, 20]

I could do it this way, with two subroutines.

SUB PROCESSA(INT M[10,10])
.
.

SUB PROCESSB(INT M[20, 20])
.
.

Or, like you said, I could rely on the fact that the arrays are global.

SUB PROCESSA()
.
.

SUB PROCESSB()
.
.

Either way, I need two subroutines, to process two square two dimensional integer arrays, of different sizes.

If I insist on having only one subroutine, then I have to pass a flag to the subroutine, to specify which array I want to process.

SUB PROCESS(INT FLAG)
.
.

If I pass a flag to a single subroutine, then I still have to put slight variations of the same code, twice, within the subroutine.

I think, that is the drawback of having to "hardwire" code.

Dan.
"You can't cheat an honest man. Never give a sucker an even break, or smarten up a chump."  -  W.C. Fields

LarryMc

From the EBasic Help file:

QuotePassing Arrays

Arrays require special handling when passing them to a subroutine. The subroutine needs to know the dimensions of the array at compile time. For single dimensioned arrays it is not necessary to supply a dimension and an empty bracket set will suffice '[ ]'. All arrays are passed by reference so anything done to the array in the subroutine will modify the array passed to it.


DEF myarray[10,50] as INT
DEF floatarray[10] as FLOAT
ArraysAreFun(myarray, floatarray)
END

SUB ArraysAreFun(iArray[10,50] as INT, flArray[] as FLOAT )
'do something with the arrays
RETURN
ENDSUB

Quote from: danbaron on July 26, 2009, 05:12:21 PM
...If I pass a flag to a single subroutine, then I still have to put slight variations of the same code, twice, within the subroutine.

I think, that is the drawback of having to "hardwire" code....
And then there are those of us who see it a plus that we can combine similiar routines into one routine and pass a flag to distinguish between the two,
A simple IF statement brackets the code that is different ( or in the case of more than two options the use of a SELECT/CASE structure) makes it easy to minimize redundant code.

And as for multiple arrays of different sizes...
If they are not used at the same time then use just one that is defined at the largest possible size.
You can pass the particular size variables as separate parameters to the routine for internal processing.

And that only scratches the surface of how it could be handled.

Larry
LarryMc
Larry McCaughn :)
Author of IWB+, Custom Button Designer library, Custom Chart Designer library, Snippet Manager, IWGrid control library, LM_Image control library

billhsln

There is always the way of just sending the address of the starting point of the array and then also sending the x, y and lenth of y (ly).

Then the array can be accessed by:

array[x*ly+y]

so, when Orig_Array[4,4], Orig_Array[0,0] = array[0*4+0] = (0), Orig_Array[2,1] = array[2*4+1] = (9), Orig_Array[3,3] = array[3*4+3] = (15), etc.

if, Second_Array[9,9], Second_Array[0,0] = array[0*9+0] = (0), Second_Array[2,1] = array[2*9+1] = (19), Second_Array[3,3] = array[3*9+3] = (30), etc.

Old programmers trick, used before some languages could do multi dimensioned arrays.

Bill
When all else fails, get a bigger hammer.

Ionic Wind Support Team

Don't anyone jump to any conclusions before I have had a chance to review the topic.

A constant should work, and if it doesn't then it's something I need to fix.  Obviously not something that comes up on a regular basis, especially for my own code because I don't use static arrays. Simpler for me to use new/delete and just calculate the proper offset.

Paul.

Ionic Wind Support Team

Ionic Wind Support Team

July 26, 2009, 09:05:41 PM #6 Last Edit: July 26, 2009, 09:09:50 PM by Paul Turley
Here are a few solutions for you until I get a chance to review the compiler code.  This can handle two arbitrary array dimensions defined as separate constants.


CONST D1 = 3
CONST D2 = 3
'$DEFINE D 3

DEF A[D1,D2]:INT
INT I,J
STRING S

FOR I = 0 TO D1-1
FOR J = 0 TO D2-1
A[I,J] = I*D1 + J+1
PRINT A[I,J]
NEXT J
NEXT I
PRINT
PRINTARRAY(A)
INPUT S
END

SUB PRINTARRAY(M as pointer)
INT I,J
FOR I = 0 TO D1-1
FOR J = 0 TO D2-1
PRINT *<INT>(M+(J*D1+I)*4)
NEXT J
NEXT I
ENDSUB


The calculation line isn't as hard as it looks.  The address of a particular element of a 2 dimension array is specified as:

address of array + (index2 * dimension1 + index1)*size of data

You can even pass the size of the array as variables to make it a generic subroutine:


SUB PRINTARRAY(M as pointer, dim1 as int, dim2 as int)
INT I,J
FOR I = 0 TO dim1-1
FOR J = 0 TO dim2-1
PRINT *<INT>(M+(J*dim1+I)*4)
NEXT J
NEXT I
ENDSUB


Which will lead you into dynamically creating arrays eventually, and not having the compile time limitation of static arrays.  Here is an example of complete dynamic solution:


CONST D1 = 3
CONST D2 = 3
'$DEFINE D 3

DEF A as pointer
INT I,J
STRING S
A = NEW(INT,D1*D2)

FOR I = 0 TO D1-1
FOR J = 0 TO D2-1
*<INT>(A+(J*D1+I)*4) = I*D1 + J+1
PRINT *<INT>(A+(J*D1+I)*4)
NEXT J
NEXT I
PRINT
PRINTARRAY(A,D1,D2)
INPUT S
DELETE A
END

SUB PRINTARRAY(M as pointer, dim1 as int, dim2 as int)
INT I,J
FOR I = 0 TO dim1-1
FOR J = 0 TO dim2-1
PRINT *<INT>(M+(J*dim1+I)*4)
NEXT J
NEXT I
ENDSUB


Now I wouldn't actually do it that way, but it is there for you to learn.  I prefer more 'elegant' solutions, using the tools that are available.  For example:


int D1 = 3
int D2 = 3
'$DEFINE D 3

TYPE DYNARRAY
int dim1
int dim2
int size
pointer mem
ENDTYPE

DEF A as DYNARRAY
DEF pData as POINTER
INT I,J
STRING S
A = CreateArray(D1,D2,len(INT))

FOR I = 0 TO D1-1
FOR J = 0 TO D2-1
pData = ArrayPtr(A,I,J)
*<INT>pData = I*D1 + J+1
PRINT *<INT>pData
NEXT J
NEXT I
PRINT
PRINTARRAY(A)
INPUT S
END

SUB PRINTARRAY(A as DYNARRAY)
INT I,J
POINTER pData
FOR I = 0 TO A.dim1-1
FOR J = 0 TO A.dim2-1
pData = ArrayPtr(A,I,J)
PRINT *<INT>pData
NEXT J
NEXT I
ENDSUB

SUB CreateArray(dim1 as int,dim2 as int,size as INT),DYNARRAY
DYNARRAY ret
ret.mem = NEW(char, dim1 * dim2 * size)
ret.dim1 = dim1
ret.dim2 = dim2
ret.size = size
OnExit(&DeleteArray,ret.mem)
return ret
ENDSUB

SUB ArrayPtr(array as DYNARRAY,index1 as int,index2 as int),POINTER
if array.mem
return array.mem + (index2*array.dim1+index1)*array.size
endif
'error here if array used without being created.
return 0
ENDSUB

SUB DeleteArray(pointer mem)
delete mem
ENDSUB


The above is one I have used in the past, because the array can handle any type of data, including UDT's, and can be of any arbitrary dimensions.  You can expand on it in many ways, such as adding error checking for out of bounds access in the ArrayPtr subroutine, adding functions to sort the array, etc.  You'll find that if you code like this that expanding your code in the future is much easier.

The array is automatically deleted for you when your program ends, so you don't need to worry about memory leaks.

Later,
Paul.
Ionic Wind Support Team

danbaron

July 27, 2009, 03:03:46 AM #7 Last Edit: July 27, 2009, 03:35:40 AM by danbaron
Now you guys are talking!

I don't know why everyone says all of you are terrible (Ha! Ha!).

I tried Larry's way,

"And as for multiple arrays of different sizes... If they are not used
at the same time then use just one that is defined at the largest
possible size. You can pass the particular size variables as separate
parameters to the routine for internal processing."

It is very simple, and works perfectly.

I think, Bill and Paul basically both showed how to simulate a 2D array,
using a 1D array, and address offsets.

I learned a lot more about what I can do with pointers in EB.

And Paul even showed how to use TYPE, to make generic arrays.

All in all, I call that a real good lesson.

Before I read your posts, I had decided to represent a 2D array, using a
1D array, by transforming 2D indexing, to 1D; as shown in the little
program below.

Now, I have more possibilities.

(I'll post a little poem in "General/Off Topic", to express my glee.)

Thanks,
Dan.


'----------------------------------------------------------------------------------
'FILE = 1DFOR2D.EBA

'EB CONSOLE PROGRAM.

'USES A 1D ARRAY TO SIMULATE A 2D ARRAY,
'BY TRANSFORMING 2D INDEXING, TO 1D.

'----------------------------------------------------------------------------------

CONST M = 100
CONST N = 200
CONST MN = M * N

'I LIKE TO START ARRAY INDEXING AT 1.
CONST MNP1 = MN + 1

'----------------------------------------------------------------------------------

INT A[MNP1]
INT I, TEST
STRING S

'----------------------------------------------------------------------------------

'SET EACH ARRAY VALUE EQUAL TO ITS 1D INDEX.
FOR I = 1 TO MN
A[I] = I
NEXT I


'TEST GETARRAYVAL()
'USE A[17501].
'ITS VALUE IS 17501.

'INTEGER DIVISION
'I = (17501 / 200) + 1 = 88
'J =  17501 % 200 = 101

TEST = GETARRAYVAL(A, M, N, 88, 101)
PRINT TEST

'TEST (ABOVE) DOES PRINT 17501.

'TEST SETARRAYVAL()
'USE A[9043].
'ITS VALUE IS 9043.
'I = (9043 / 200) + 1 = 46
'J =  9043 % 200 = 43

SETARRAYVAL(A, M, N, 46, 43, 1234)
TEST = A[9043]
PRINT TEST

'TEST (ABOVE) DOES PRINT 1234.

INPUT S

END

'----------------------------------------------------------------------------------

SUB GETINDEX(INT DIM1, INT DIM2, INT I, INT J), INT
'ASSUMES ARRAY INDICES BEGIN AT 1.

'CHECK FOR OUT OF RANGE.
IF I > DIM1 THEN RETURN 0
IF J > DIM2 THEN RETURN 0

RETURN (I - 1) * DIM2 + J
ENDSUB

'----------------------------------------------------------------------------------

SUB SETARRAYVAL(M[] AS INT, INT DIM1, INT DIM2, INT I, INT J, INT V)
INT INDEX
INDEX = GETINDEX(DIM1, DIM2, I, J)
M[INDEX] = V
ENDSUB

'----------------------------------------------------------------------------------

SUB GETARRAYVAL(M[] AS INT, INT DIM1, INT DIM2, INT I, INT J), INT
INT INDEX
INDEX = GETINDEX(DIM1, DIM2, I, J)
RETURN M[INDEX]
ENDSUB

'----------------------------------------------------------------------------------
'----------------------------------------------------------------------------------
"You can't cheat an honest man. Never give a sucker an even break, or smarten up a chump."  -  W.C. Fields