Я пытаюсь выполнить некоторые преобразования с многомерным массивом на C #, и я проверил обсуждение ConvertAll и двухмерные массивы. я нашел это Array.ConvertAll
по-прежнему не поддерживает случаи многомерного массива. Таким образом, вот экспериментальная реализация для обработки процесса преобразования многомерного массива.
Экспериментальная реализация
Экспериментальная реализация приведена ниже.
public class Converters
{
public static TOutput[,] ConvertAll<TInput, TOutput>(TInput[,] array, Converter<TInput, TOutput> converter)
{
if (array is null)
{
throw new ArgumentNullException(nameof(array));
}
if (converter is null)
{
throw new ArgumentNullException(nameof(converter));
}
var output = new TOutput[array.GetLongLength(0), array.GetLongLength(1)];
for (long row = 0; row < array.GetLongLength(0); row++)
{
for (long column = 0; column < array.GetLongLength(1); column++)
{
output[row, column] = converter(array[row, column]);
}
}
return output;
}
public static TOutput[,,] ConvertAll<TInput, TOutput>(TInput[,,] array, Converter<TInput, TOutput> converter)
{
if (array is null)
{
throw new ArgumentNullException(nameof(array));
}
if (converter is null)
{
throw new ArgumentNullException(nameof(converter));
}
var output = new TOutput[array.GetLongLength(0), array.GetLongLength(1), array.GetLongLength(2)];
for (long dim1 = 0; dim1 < array.GetLongLength(0); dim1++)
{
for (long dim2 = 0; dim2 < array.GetLongLength(1); dim2++)
{
for (long dim3 = 0; dim3 < array.GetLongLength(2); dim3++)
{
output[dim1, dim2, dim3] = converter(array[dim1, dim2, dim3]);
}
}
}
return output;
}
public static TOutput[,,,] ConvertAll<TInput, TOutput>(TInput[,,,] array, Converter<TInput, TOutput> converter)
{
if (array is null)
{
throw new ArgumentNullException(nameof(array));
}
if (converter is null)
{
throw new ArgumentNullException(nameof(converter));
}
var output = new TOutput[array.GetLongLength(0), array.GetLongLength(1), array.GetLongLength(2), array.GetLongLength(3)];
for (long dim1 = 0; dim1 < array.GetLongLength(0); dim1++)
{
for (long dim2 = 0; dim2 < array.GetLongLength(1); dim2++)
{
for (long dim3 = 0; dim3 < array.GetLongLength(2); dim3++)
{
for (long dim4 = 0; dim4 < array.GetLongLength(3); dim4++)
{
output[dim1, dim2, dim3, dim4] = converter(array[dim1, dim2, dim3, dim4]);
}
}
}
}
return output;
}
public static TOutput[,,,,] ConvertAll<TInput, TOutput>(TInput[,,,,] array, Converter<TInput, TOutput> converter)
{
if (array is null)
{
throw new ArgumentNullException(nameof(array));
}
if (converter is null)
{
throw new ArgumentNullException(nameof(converter));
}
var output = new TOutput[array.GetLongLength(0), array.GetLongLength(1), array.GetLongLength(2), array.GetLongLength(3), array.GetLongLength(4)];
for (long dim1 = 0; dim1 < array.GetLongLength(0); dim1++)
{
for (long dim2 = 0; dim2 < array.GetLongLength(1); dim2++)
{
for (long dim3 = 0; dim3 < array.GetLongLength(2); dim3++)
{
for (long dim4 = 0; dim4 < array.GetLongLength(3); dim4++)
{
for (long dim5 = 0; dim5 < array.GetLongLength(4); dim5++)
{
output[dim1, dim2, dim3, dim4, dim5] = converter(array[dim1, dim2, dim3, dim4, dim5]);
}
}
}
}
}
return output;
}
public static TOutput[,,,,,] ConvertAll<TInput, TOutput>(TInput[,,,,,] array, Converter<TInput, TOutput> converter)
{
if (array is null)
{
throw new ArgumentNullException(nameof(array));
}
if (converter is null)
{
throw new ArgumentNullException(nameof(converter));
}
var output = new TOutput[array.GetLongLength(0), array.GetLongLength(1), array.GetLongLength(2), array.GetLongLength(3), array.GetLongLength(4), array.GetLongLength(5)];
for (long dim1 = 0; dim1 < array.GetLongLength(0); dim1++)
{
for (long dim2 = 0; dim2 < array.GetLongLength(1); dim2++)
{
for (long dim3 = 0; dim3 < array.GetLongLength(2); dim3++)
{
for (long dim4 = 0; dim4 < array.GetLongLength(3); dim4++)
{
for (long dim5 = 0; dim5 < array.GetLongLength(4); dim5++)
{
for (long dim6 = 0; dim6 < array.GetLongLength(5); dim6++)
{
output[dim1, dim2, dim3, dim4, dim5, dim6] = converter(array[dim1, dim2, dim3, dim4, dim5, dim6]);
}
}
}
}
}
}
return output;
}
}
Тестовые примеры
Перечисленные здесь тестовые случаи включают двумерный случай, трехмерный случай и четырехмерный случай.
Console.WriteLine("Two dimensional case");
int[,] ii = { { 0, 1 }, { 2, 3 } };
double[,] dd = Converters.ConvertAll(ii, x => x + 0.1);
for (long row = 0; row < dd.GetLongLength(0); row++)
{
for (long column = 0; column < dd.GetLongLength(1); column++)
{
Console.Write(dd[row, column].ToString() + "t");
}
Console.WriteLine();
}
Console.WriteLine();
Console.WriteLine("Three dimensional case");
int[,,] iii = { { { 0, 1 }, { 2, 3 } } , { { 0, 1 }, { 2, 3 } } };
double[,,] ddd = Converters.ConvertAll(iii, x => x + 0.1);
for (long dim1 = 0; dim1 < ddd.GetLongLength(0); dim1++)
{
Console.WriteLine($"dim1 = {dim1}");
for (long dim2 = 0; dim2 < ddd.GetLongLength(0); dim2++)
{
for (long dim3 = 0; dim3 < ddd.GetLongLength(1); dim3++)
{
Console.Write(ddd[dim1, dim2, dim3].ToString() + "t");
}
Console.WriteLine();
}
Console.WriteLine();
}
Console.WriteLine("Four dimensional case");
int[,,,] iiii = { { { { 0, 1 }, { 2, 3 } }, { { 0, 1 }, { 2, 3 } } }, { { { 0, 1 }, { 2, 3 } }, { { 0, 1 }, { 2, 3 } } } };
var dddd = Converters.ConvertAll(iiii, x => x + 0.1);
for (long dim1 = 0; dim1 < dddd.GetLongLength(0); dim1++)
{
for (long dim2 = 0; dim2 < dddd.GetLongLength(1); dim2++)
{
Console.WriteLine($"dim1 = {dim1}, dim2 = {dim2}");
for (long dim3 = 0; dim3 < dddd.GetLongLength(2); dim3++)
{
for (long dim4 = 0; dim4 < dddd.GetLongLength(3); dim4++)
{
Console.Write(dddd[dim1, dim2, dim3, dim4].ToString() + "t");
}
Console.WriteLine();
}
Console.WriteLine();
}
Console.WriteLine();
}
Результат тестового кода выше:
Two dimensional case
0.1 1.1
2.1 3.1
Three dimensional case
dim1 = 0
0.1 1.1
2.1 3.1
dim1 = 1
0.1 1.1
2.1 3.1
Four dimensional case
dim1 = 0, dim2 = 0
0.1 1.1
2.1 3.1
dim1 = 0, dim2 = 1
0.1 1.1
2.1 3.1
dim1 = 1, dim2 = 0
0.1 1.1
2.1 3.1
dim1 = 1, dim2 = 1
0.1 1.1
2.1 3.1
Все предложения приветствуются. Если есть какие-либо проблемы с потенциальным недостатком или ненужными накладными расходами реализованных методов, сообщите мне.
2 ответа
Хочу представить альтернативное решение. В .NET массивы реализуют IEnumerable
. Это позволяет нам перебирать многомерные массивы, как если бы они были плоскими. Обратной стороной является то, что мы должны вычислять индексы в выходном массиве.
Вот пример трехмерного массива:
public static TOutput[,,] ConvertAll<TInput, TOutput>(TInput[,,] input,
Converter<TInput, TOutput> convert)
{
int N0 = input.GetLength(0);
int N1 = input.GetLength(1);
int N2 = input.GetLength(2);
var output = new TOutput[N0, N1, N2];
int n = 0;
foreach (var item in input) {
int i = n / (N1 * N2);
int j = n / N2 % N1;
int k = n % N2;
output[i, j, k] = convert(item);
n++;
}
return output;
}
Идея состоит в том, чтобы для индекса данного измерения разделить индекс в уплощенном перечислении. n
на произведение длин более высоких измерений (как целочисленное деление), а затем взять модуль своей собственной длины. Модуль не является обязательным для первого индекса, так как результат деления никогда не превысит диапазон индекса.
Я сделал тестовые примеры с размерами разной длины, чтобы убедиться, что правильно рассчитал индекс.
public static void Test()
{
var input = new int[2, 3, 4] {
{ { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } },
{ { 13, 14, 15, 16 }, { 17, 18, 19, 20 }, { 21, 22, 23, 24 } }
};
var output = ConvertAll<int, double>(input, i => (double)i);
WriteTest(output);
Console.WriteLine();
var input2 = new int[4, 3, 2] {
{ { 1, 2 }, { 3, 4 }, { 5, 6 } },
{ { 7, 8 }, { 9, 10 }, { 11, 12 } },
{ { 13, 14 }, { 15, 16 }, { 17, 18 } },
{ { 19, 20 }, { 21, 22 }, { 23, 24 } }
};
var output2 = ConvertAll<int, double>(input2, i => (double)i);
WriteTest(output2);
}
private static void WriteTest(double[,,] array)
{
int N0 = array.GetLength(0);
int N1 = array.GetLength(1);
int N2 = array.GetLength(2);
for (int i = 0; i < N0; i++) {
for (int j = 0; j < N1; j++) {
for (int k = 0; k < N2; k++) {
Console.WriteLine($"array[{i},{j},{k}] = {array[i, j, k]}");
}
}
}
}
Ваш код повторяется. Чтобы решить эту проблему, я покажу несколько простых читов, связанных с массивами.
- Как сказал @Olivier,
Array
орудияIEnumerable
. Таким образом, вы можете повторить его с помощьюforeach
. Array
является родительским типом для любого массива. Затем вы можете преобразовать любой массив вArray
.- Метод
Buffer.BlockCopy
может копировать байты из массива в другой массив независимо от типа данных, но принимает размер элемента данных. Marshal.SizeOf
вычисляет размер элемента в байтах поType
и позволяет использовать универсальные типы. Вот ограничение, вы не можете использовать здесь ссылочные типы, такие как классы.
Решение — единственный метод
static Array ConvertArray<T, TResult>(Array array, Converter<T, TResult> converter)
where T : unmanaged // these two lines protects you from passing unsupported types to the method
where TResult : unmanaged
{
int[] dimensions = new int[array.Rank];
for (int i = 0; i < array.Rank; i++)
dimensions[i] = array.GetLength(i);
Array result = Array.CreateInstance(typeof(TResult), dimensions); // instantiates the resulting array
TResult[] tmp = new TResult[1]; // wrap an item with array to feed Buffer.BlockCopy
int offset = 0;
int itemSize = Marshal.SizeOf(typeof(TResult));
foreach (T item in array)
{
tmp[0] = converter(item);
Buffer.BlockCopy(tmp, 0, result, offset * itemSize, itemSize);
offset++;
}
return result;
}
Этот метод принимает любой массив любого количества измерений любого ValueType
элементы, например, любые примитивные типы или структуры примитивных типов.
Изменились три строки кода тестовых случаев, остальные строки остались прежними.
double[,] dd = (double[,])ConvertArray<int, double>(ii, x => x + 0.1);
double[,,] ddd = (double[,,])ConvertArray<int, double>(iii, x => x + 0.1);
var dddd = (double[,,,])ConvertArray<int, double>(iiii, x => x + 0.1);
Вывод на консоль точно такой же.
Подпись ConvertArray
может выглядеть недружелюбно, поэтому вы можете обернуть его желаемым методом, например:
public static TOutput[,] ConvertAll<TInput, TOutput>(TInput[,] array, Converter<TInput, TOutput> converter)
where TInput : unmanaged
where TOutput : unmanaged
{
return (TOutput[,])ConvertArray(array, converter);
}
Другой пример, иллюстрирующий преобразование многомерного массива с .Cast<T>()
и Buffer.BlockCopy()
.
public static TOutput[,] ConvertAll<TInput, TOutput>(TInput[,] array, Converter<TInput, TOutput> converter)
where TInput : unmanaged
where TOutput : unmanaged
{
TOutput[] tmp = Array.ConvertAll(array.Cast<TInput>().ToArray(), converter);
TOutput[,] result = new TOutput[array.GetLength(0), array.GetLength(1)];
int itemSize = Marshal.SizeOf(typeof(TOutput));
Buffer.BlockCopy(tmp, 0, result, 0, array.Length * itemSize);
return result;
}
Отрицательная сторона этой реализации — вдвое большее потребление памяти для выделения двух одномерных массивов, содержащих одни и те же данные. Я не рекомендую это решение, потому что оно не оптимизировано, я дал его только для того, чтобы показать, как преобразовать многомерный массив в одномерный и наоборот.
Примечание: если вы используете не общий тип, а какой-то конкретный примитив, напримерint
, вы можете напрямую использовать sizeof(int)
вместо того Marshal.SizeOf(typeof(int))
. Последний полезен только с непримитивными или универсальными типами.