Home > Software engineering >  Expose a column from a 2D array without copying
Expose a column from a 2D array without copying

Time:09-23

I have a 2D array, and I'd like to expose the values within a single column as a 1D array. The code should not involve copying as this is a data processing system that will involve hundreds of Mb of data. The reason for wanting to expose a single column of data is really just to avoid calling code from having access to the entire 2D array.

I found the Span2D<T> type (CommunityToolkit.HighPerformance NuGet package):

var rawData = new double[,];
...
Span2D<double> span = rawData;

The only potentially useful member I found was GetColumn():

var column = span.GetColumn(1);

However this returns an enumerator, so the only way I can see of exposing the data as an array is to use .ToArray(). Is it possible to do what I'm trying to achieve, either with Span2D<> or some other approach?

CodePudding user response:

If you can change the consuming code to use an IReadOnlyList<T>, it's not too much work to write a wrapper class that will expose a column of a matrix (2D array) as an IReadOnlyList<T> vector.

This does add the proverbial "extra level of indirection", but it does also mean that the elements are returned without the need for any array allocations.

It's essentially working the same way as List<T> does - by wrapping an array - so it might be plenty fast enough for your needs.

Here's a sample implementation, with error handling elided for brevity:

(Also you can try this online)

public sealed class ArrayColumn<T>: IReadOnlyList<T>
{
    public ArrayColumn(T[,] matrix, int column)
    {
        _matrix = matrix;
        _column = column;
        Count   = _matrix.GetLength(0);
    }

    public IEnumerator<T> GetEnumerator()
    {
        IEnumerable<T> items()
        {
            for (int row = 0; row < Count;   row)
            {
                yield return _matrix[row, _column];
            }
        }

        return items().GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public int Count { get; }

    public T this[int index] => _matrix[index, _column];

    readonly T[,] _matrix;
    readonly int  _column;
}

With that implementation you can take a vertical slice from a matrix (2D array) and present it as a vector (1D array):

public static void Main()
{
    var matrix = new string[5, 10];

    for (int row = 0; row < matrix.GetLength(0);   row)
    {
        for (int col = 0; col < matrix.GetLength(1);   col)
        {
            matrix[row, col] = $"{row}, {col}";
        }
    }

    var col3 = new ArrayColumn<string>(matrix, column: 3); // Grab column 3

    for (int col = 0; col < col3.Count;    col)
    {
        Console.WriteLine(col3[col]);
    }

    Console.WriteLine("-----------------");

    foreach (var element in col3)
    {
        Console.WriteLine(element);
    }
}

Output:

0, 3
1, 3
2, 3
3, 3
4, 3
-----------------
0, 3
1, 3
2, 3
3, 3
4, 3
  • Related