N-мерная матрица

Я хочу создать n-мерный массив двойников. Во время компиляции число измерений n неизвестно.

Я закончил определение массива как словаря, причем ключ был массивом int, соответствующим различным осям (поэтому в трехмерном массиве я бы поставил [5, 2, 3], чтобы получить двойную в (5, 2, 3) в массиве.

Однако мне также нужно заполнить словарь двойниками от (0, 0,... 0) до (m1, m2,... mn), где m1 - mn - длина каждой оси.

Моя первоначальная идея заключалась в создании вложенных for-loops, но поскольку я все еще не знаю, сколько мне понадобится (1 для каждого измерения), я не могу сделать это во время компиляции.

Надеюсь, я сформулировал вопрос понятным образом, но не стесняйтесь просить меня разработать детали.

Ответ 1

Быстрое наблюдение по этому вопросу:

Мы успешно использовали метод Array.CreateInstance, но, как предсказал кто-то, он был довольно неэффективен и дополнительно создавал проблемы с читабельностью.

Вместо этого мы разработали метод, в котором n-мерный массив преобразуется в 1-мерный (нормальный) массив.

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

Это, по сути, обобщение на преобразование декартовых координат в одно целое и обратно.

Наше тестирование не формализовано, поэтому любое ускорение, которое мы получили, полностью анекдотично, но для моей машины он дал примерно 30-50% ускорения, в зависимости от размера выборки, и читаемость кода улучшилась на широкий диапазон.

Надеюсь, это поможет любому, кто наткнулся на этот вопрос.

Ответ 2

Чтобы создать n-мерный массив, вы можете использовать метод Array.CreateInstance:

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

Чтобы заполнить массивы, вы можете использовать свойство Rank и метод GetLength, чтобы вернуть длину текущего измерения, используя пару вложенных циклов для выполнения O (n ^ m) algo (предупреждение - непроверенные):

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

Ответ 3

Почему бы вам просто не использовать многомерный массив: double[,,] array = new double[a,b,c]? Все элементы массива автоматически инициализируются для 0.0.

В качестве альтернативы вы можете использовать jagged array double[][][], но каждый субмассив должен быть инициализирован в цикле for:

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: не удалось определить, сколько измерений было временем выполнения. Добавлен еще один ответ.

Ответ 4

С помощью этого метода вы можете создавать n-мерные зубчатые массивы любого типа.

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