I want to create an n-dimensional array of doubles. At compile-time, the number of dimensions n is not known.
I ended up defining the array as a dictionary, with the key being an array of ints corresponding to the different axes (so in a 3-dimensional array, I'd supply [5, 2, 3] to get the double at (5, 2, 3) in the array.
However, I also need to populate the dictionary with doubles from (0, 0, ... 0) to (m1, m2, ... mn), where m1 to mn is the length of each axis.
My initial idea was to create nested for-loops, but as I still don't know how many I'd need (1 for each dimension), I can't do this at compile-time.
I hope I've formulated the question in an understandable manner, but feel free to ask me to elaborate parts.
-
It will be used in Factorial Markov Random Field calculations, where we have n layers with m segments in each. We then want to make a matrix of probabilities for each observed value, or in the case of continuous values, two matrices, for means and variance, for each observed value.SimonPip– SimonPip2010年11月16日 12:07:00 +00:00Commented Nov 16, 2010 at 12:07
4 Answers 4
To create a n-dimensional array, you can use the Array.CreateInstance method:
Array array = Array.CreateInstance(typeof(double), 5, 3, 2, 8, 7, 32));
array.SetValue(0.5d, 0, 0, 0, 0, 0, 0);
double val1 = (double)array.GetValue(0, 0, 0, 0, 0, 0);
array.SetValue(1.5d, 1, 2, 1, 6, 0, 30);
double val2 = (double)array.GetValue(1, 2, 1, 6, 0, 30);
To populate the arrays, you can use the Rank property and GetLength method to return the length of the current dimension, using a couple of nested for loops to do a O(n^m) algo (warning - untested):
private bool Increment(Array array, int[] idxs, int dim) {
if (dim >= array.Rank) return false;
if (++idxs[idxs.Length-dim-1] == array.GetLength(dim)) {
idxs[idxs.Length-dim-1] = 0;
return Increment(array, idxs, dim+1);
}
return true;
}
Array array = Array.CreateInstance(typeof(double), ...);
int[] idxs = new int[array.Rank];
while (Increment(array, idxs, 0)) {
array.SetValue(1d, idxs);
}
Comments
A quick followup on this matter:
We used the Array.CreateInstance method with success, but as someone predicted, it was fairly inefficient, and additionally created readability problems.
Instead, we have developed a method, where the n-dimensional array is converted into a 1-dimensional (normal) array.
public static int NDToOneD(int[] indices, int[] lengths)
{
int ID = 0;
for (int i = 0; i < indices.Length; i++)
{
int offset = 1;
for (int j = 0; j < i; j++)
{
offset *= lengths[j];
}
ID += indices[i] * offset;
}
return ID;
}
1DtoND(int[] indices, int[] arrayLengths)
{
int[] indices = new int[lengths.Length];
for (int i = lengths.Length - 1; i >= 0; i--)
{
int offset = 1;
for (int j = 0; j < i; j++)
{
offset *= lengths[j];
}
int remainder = ID % offset;
indices[i] = (ID - remainder) / offset;
ID = remainder;
}
return indices;
}
This is essentially a generalisation on the conversion of cartesian coordinates to a single integer and back again.
Our testing is not formalized, so any speedup we have gained is entirely anecdotal, but for my machine, it has given about a 30-50% speedup, depending on the sample size, and the readability of the code has improved by a wide margin.
Hope this helps anyone who stumbles upon this question.
Comments
With this method, you can create n-dimensional jagged arrays of any type.
public static Array CreateJaggedArray<T>(params int[] lengths)
{
if(lengths.Length < 1)
throw new ArgumentOutOfRangeException(nameof(lengths));
void Populate(Array array, int index)
{
for (int i = 0; i < array.Length; i++)
{
Array element = (Array)Activator.CreateInstance(array.GetType().GetElementType(), lengths[index]);
array.SetValue(element, i);
if (index + 1 < lengths.Length)
Populate(element, index + 1);
}
}
Type retType = typeof(T);
for (var i = 0; i < lengths.Length; i++)
retType = retType.MakeArrayType();
Array ret = (Array)Activator.CreateInstance(retType, lengths[0]);
if (lengths.Length > 1)
Populate(ret, 1);
return ret;
}
Comments
Why don't you just use a multidimensional array: double[,,] array = new double[a,b,c]? All the array elements are automatically initialized to 0.0 for you.
Alternatively, you could use a jagged array double[][][], but each sub-array will need to be initialized in a for loop:
int a, b, c;
double[][][] array = new double[a][][];
for (int i=0; i<a; i++) {
double[i] = new double[b][];
for (int j=0; j<b; j++) {
double[i][j] = new double[c];
}
}
EDIT: didn't realise number of dimensions was run-time. Added another answer above.