简体   繁体   中英

n-dimensional Array

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.

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);
}

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.

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.

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;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM