9
\$\begingroup\$

I am trying to perform some conversions with multidimensional array in C# and I have checked the discussion ConvertAll and 2-dimensional arrays. I found that Array.ConvertAll still not support multidimensional array cases. Therefore, here's an experimental implementation for handling the process of conversions on multidimensional array.

The experimental implementation

The experimental implementation is as below.

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

Test cases

The test cases listed here include two dimensional case, three dimensional case and four dimensional case.

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

The output of the test code above:

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

All suggestions are welcome. If there is any issue about potential drawback or unnecessary overhead of the implemented methods, please let me know.

\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

I want to present an alternative solution. In .NET, arrays implement IEnumerable. This allows us to iterate multi dimensional arrays as if they were flat. The downside is that we must calculate the indexes in the output array.

Here is an example for a 3-d array:

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

The idea is, for the index of a given dimension, to divide the index in the flattened enumeration n by the product of the lengths of the higher dimensions (as an integer division) and then to take the modulus of its own length. The modulus is not necessary for the first index, as the result of the division will never exceed the index range.


I made test cases with dimensions having different lengths, to be sure I got the index calculation right.

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]}");
            }
        }
    }
}
\$\endgroup\$
5
\$\begingroup\$

Your code is repetitive. To solve that I'll show some array-related easy cheats.

  • As @Olivier said, Array implements IEnumerable. Thus you can iterate it with foreach.
  • Array is parent type for any array. Then you may upcast any array to Array.
  • Method Buffer.BlockCopy can copy bytes from array to another array regardless of data type but accepts a data item size.
  • Marshal.SizeOf calculates the item size in bytes by Type and allows to use the Generic types. Here's a restriction, you can't use reference types such as classes here.

The solution is single method

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

This method accepts any array of any amount of dimensions of any ValueType items e.g. any primitive types or structs of primitive types.

Three lines of your test cases code changed, the other lines remain the same

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

The console output is exactly the same.


The signature of the ConvertArray method may look unfriendly, so you can wrap it with the desired method, for example:

public static TOutput[,] ConvertAll<TInput, TOutput>(TInput[,] array, Converter<TInput, TOutput> converter)
    where TInput : unmanaged
    where TOutput : unmanaged
{
    return (TOutput[,])ConvertArray(array, converter);
}

Another example to illustrate multi-dimentional array conversion with .Cast<T>() and 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;
}

The negative side of this implementation is twice more memory consumption to allocate two single-dimension arrays that contains yhe same data. I don't recommend this solution because it's not optimized, i gave it just to show how to convert multi-dimensional array to single-dimensional and vice-versa.

Note: in case you're not using Generic type but some concrete primitive e.g.int, you may directly use sizeof(int) instead of Marshal.SizeOf(typeof(int)). The last one is useful only with non-primitive or generic types.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.