Реализация методов ConvertAll для многомерного массива в C #

Я пытаюсь выполнить некоторые преобразования с многомерным массивом на 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 ответа
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)). Последний полезен только с непримитивными или универсальными типами.

      Добавить комментарий

      Ваш адрес email не будет опубликован. Обязательные поля помечены *