PDA

View Full Version : Get total size of multidimensional array



hwnd
18-10-2012, 10:57 PM
Hi.
I encountered a problem, where i need to get size of 3D array, because i will use size of this to "SetLength" of other dynamic array (1D).

What i have is this:
myarray[0..255, 0..255, 0..7] of tmyRecord;
dynarray: array of tmyObject;

3D array is used to render 3D map. Because i render only specific range (not whole map at once), the X, Y ranges may vary. They could be for ex: X: 17 - 24, Y: 0 - 7.
Z is always max 7.

Because of that i must sum them up: X * Y * Z. I get some value.
And then i set the length of dynamic array to this sum.

I mean


sum:= X * Y * Z;
SetLength(dynarray, sum);
But is it safe and correct way to do?

What i need is the SUM of NR_OF_OBJECTS within specified X,Y,Z range.


Should i be aware of something? Crashes, leaks?

Im just not sure if im doing it correctly.


Thanks for any help.

AthenaOfDelphi
19-10-2012, 12:35 AM
Hi hwnd,

On the setLength front, you should (unless I'm mistaken) always remember to setLength to 0 when you're finished with the array.

Another option you have though is to use a single array in the first place and use a function to generate an index when you access it using x,y,z. If you use your example dimensions, the function could look like this:-



function getIndex(x,y,z:integer):integer;
begin
result:=(z*256*256)+(y*256)+x;
end;


A faster, possibly more efficient variation on this could be:-



function getIndexFast(x,y,z:integer):integer;
begin
result:=(z shl 16)+(y shl 8)+x;
end;


Then you use a single dimensional array that is 256x256x8 elements in size.

The exact formula you use could vary to improve performance. For example, lets say you use the items inside three loops like this:-



for x:=0 to 255 do
begin
for y:=0 to 255 do
begin
for z:=0 to 7 do
begin
// Use item here
end;
end;
end;


Then, to improve performance you could do this:-



function getBaseIndex(x,y:integer):integer;
begin
result:=(x shl 11)+(y shl 3);
end;

...


var
baseIndex : integer;

...

for x:=0 to 255 do
begin
for y:=0 to 255 do
begin
baseIndex:=getBaseIndex(x,y);
for z:=0 to 7 do
begin
// Use item here

inc(baseIndex);
end;
end;
end;


Of course, if you just need to read through them in a particular order, then you can tailor the 'getIndex' function to cater for that and then simply read through from 0 to 524287. This approach of converting from separate indices to one big number can also be applied to using fully dynamic arrays based on pointers. A while ago I wrote an article that featured a class that represented a map layer. The follow up thread http://www.pascalgamedevelopment.com/showthread.php?3640 provides some example code of how to do this if you're interested.

Hope this helps.

hwnd
19-10-2012, 10:22 PM
Thank you for all this help.
I will try out what you wrote and will report the results of how it went.

Super Vegeta
19-10-2012, 10:49 PM
SetLength() with more than one length argument works too.

LP
20-10-2012, 02:19 AM
On the setLength front, you should (unless I'm mistaken) always remember to setLength to 0 when you're finished with the array.
Dynamic arrays are reference counted (http://rvelthuis.de/articles/articles-pointers.html#dynarrays), so you don't need to explicitly call SetLength to zero. They will be released automatically when the variable goes out of the scope.


Another option you have though is to use a single array in the first place and use a function to generate an index when you access it using x,y,z. If you use your example dimensions, the function could look like this:-



function getIndex(x,y,z:integer):integer;
begin
result:=(z*256*256)+(y*256)+x;
end;


A faster, possibly more efficient variation on this could be:-



function getIndexFast(x,y,z:integer):integer;
begin
result:=(z shl 16)+(y shl 8)+x;
end;


As long as optimizations are enabled, both versions will have equivalent performance as the compiler replaces 256 multipliers with bit shifts. You can confirm this by turning optimization on and then watching generated assembly code. The only difference in this case will be the second approach being more difficult to understand, so I'd recommend sticking to the first approach.

AthenaOfDelphi
20-10-2012, 12:27 PM
Dynamic arrays are reference counted (http://rvelthuis.de/articles/articles-pointers.html#dynarrays), so you don't need to explicitly call SetLength to zero. They will be released automatically when the variable goes out of the scope.

As long as optimizations are enabled, both versions will have equivalent performance as the compiler replaces 256 multipliers with bit shifts. You can confirm this by turning optimization on and then watching generated assembly code. The only difference in this case will be the second approach being more difficult to understand, so I'd recommend sticking to the first approach.

Thanks for clearing that up Lifepower :) I always err on the side of caution with things like variable clean up and also whether or not the compiler will optimise such things. I always make the assumption it won't and try and figure out the most efficient way of doing it myself.

User137
20-10-2012, 12:47 PM
SetLength() with more than one length argument works too.
But can you get it in other direction, get the length of the multidimensional array? For a while now i have stopped using Count property, and only rely on length() and high(). I know there's no problem with that if using 1-dimensional array with X,Y (,Z) multiplication.

One thing that happens with multiplication, is that when array is resized, it is harder to track what data is from before and what you should copy over from temporary array, if data must persist. (From the top of my head i can't think of single case where i would need it but still...) When i have dynamic 1-dimensional array, say length(50) and i setlength(100), then all the data in index slots 0..49 are there from before, and 50..99 are undefined (or null?).

Dan
20-10-2012, 02:34 PM
assuming you set the size of your array like this:


SetLength(MyArr, X, Y, Z); //where X, Y, Z are the dimensions of the array

you can get each dimension like this:


X := Length(MyArr);
Y := Length(MyArr[0]);
Z := Length(MyArr[0][0]);