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