(How)
C^# You Are - 8 July 2007
This week we look at multi-dimensional arrays.
- What is a multi-dimensional array? How do you declare one? Answer
A multi-dimensional array is an array with more than one dimension. A standard
array has a single dimension (length). A multi-dimensional array has a length,
width, depth, etc. A 2D array is a table. A 3D array is a cube.
You can technically have any number of dimensions.
In C# yo declare a multi-dimensional array just like a normal array except you separate
each dimension with a comma like so.
int[,] multiplicationTable = new int[12, 12];
- What is the difference between a regular array and a jagged
array? Answer
Many languages treat multi-dimensional arrays as nothing more than arrays of arrays.
Internally this might be true but programmatically the difference is quite obvious.
If a multi-dimensional array is nothing more than an array of arrays then the following
two declarations should be equivalent.
int[,] multiplicationTable1 = new int[12, 12];
int[][] multiplicationTable2 = new int[12][];
See the problem? With the second table we can only declare the length of the
first array. We would have to enumerate each of the elements to specify the
length of the second dimension. Even if we did set each element to a length
of 12 we would still have an array of arrays. We could not use either of the
variables interchangeably in function calls as they are distinct types in C#.
The first variable is a multi-dimensional array while the second is a jagged array.
Formally a jagged array is an array where the length of each element of a multi-dimensional
array varies. Thus the length of the first element of the array could be 10
while the second is 5 and the third is 8. We're talking length here and not
dimensions or ranks.
- What is the difference between row major and column major arrays? Answer
With row major arrays arrays are stored internally by rows whereas column major
arrays are stored by columns. This will have an impact on performance when
enumerating the array elements. With row major arrays it is faster to enumerate
each element of row 0 and then row 1 and then row 2, etc. With column major
arrays it is faster to enumerate column 1 and then column 2, etc.
Does it really matter? Sure it does if you are working with multi-dimensional
arrays. Logically we generally think of arrays being laid out in row major
order but it is always best to confirm. This simple program enumerates the
elements of an 1000x1000 array. Running this program reveals that C# uses
row major ordering and therefore you should always try to have the column indice
change the most for best performance. On my machine the first loop took twice
as long as the second loop.
static void Main ( string[] args )
{
int[,] multiplicationTable = new int[1000,
1000];
Stopwatch sw = new Stopwatch();
sw.Start();
for (int col = 0; col < 1000; ++col)
{
for (int row = 0; row < 1000; ++row)
multiplicationTable[row, col] = (row + 1) * (col
+ 1);
};
sw.Stop();
Debug.WriteLine(String.Format("Column major:
{0}", sw.ElapsedMilliseconds));
sw.Reset();
sw.Start();
for (int row = 0; row < 1000; ++row)
{
for (int col = 0; col < 1000; ++col)
multiplicationTable[row, col] = (row + 1) * (col
+ 1);
};
sw.Stop();
Debug.WriteLine(String.Format("Row major:
{0}", sw.ElapsedMilliseconds));
}
- How do you enumerate the elements of an array? How about a multi-dimensional
array? Answer
In both cases the foreach loop will enumerate each element of the
array whether it is a single dimension or multi-dimensional. Of course a for
loop works just as well.
As an "exercise for the reader" how does foreach compare to the
row major/column major test of earlier?
- Why can't you use the Length property with a multi-dimensional
array? Answer
You can, technically, but it is seldom useful. The Length
property specifies the total number of elements in the array and not the number
of rows in the array. This is easy to get confused since with single dimensional
arrays the number of elements matches the number of rows. For the table in
the earlier examples the length is 144 while the number of rows is only 12.
Use the GetUpperBound function to get the length of each dimension.
GetUpperBound(1) for the above table would give you 11 as there
are 12 columns in the table. Although C# starts array indice at 0 there is
no formal requirement for this to be true. Therefore to get the actual length
of each dimension you should use the following formula: GetUpperBound() - GetLowerBound()
+ 1. In the above example code 11 - 0 + 1 = 12 which is the total
length of the dimension.
- What is the rank of an array? Answer
The rank is what we would normally think of as the dimensionality of the array.
A table has a rank of 2 while a cube has a rank of 3. Given the Rank
property in combination with the GetLowerBound and GetUpperBound
functions we can determine the dimensions of any array.
Here is a handy function to dump information about an array.
static string ArrayInfo ( Array arr )
{
StringBuilder bldr = new StringBuilder();
Type typeArr = arr.GetType();
bldr.AppendFormat("{0}[",
typeArr.GetElementType().Name);
for (int nRank = 0; nRank < arr.Rank; ++nRank)
{
if (nRank > 0)
bldr.Append(",
");
bldr.Append(arr.GetUpperBound(nRank) - arr.GetLowerBound(nRank) + 1);
};
bldr.Append("]");
return bldr.ToString();
}
- How can you resize a multi-dimensional array without destroying the existing elements? Answer
If this were a single dimensional array then you would use Array.Resize
but that method does not work with multiple dimensions. Instead you have to
write your own code. This is made more complicated by the fact that the provided
method uses a ref modifier but you can't use this modifier on a
multi-dimensional array in combination with Array.
To get you started here is some starter code. This code could probably be
optimized to do efficient copying. A generic function might work as well.
I haven't tried it.
static Array Resize ( Array arr, int[] sizes )
{
if (arr.Rank != sizes.Length)
throw
new ArgumentException("Rank does not match the size of the array.");
//Get the array
information
Type eleType = arr.GetType().GetElementType();
//Allocate the new array
Array
newArr = Array.CreateInstance(eleType, sizes);
//Copy the elements one by
one
...
return newArr;
}