5

Which would be the most efficient way to convert a squared matrix like

  1 2 3 
  4 5 6
  7 8 9 

into

[1 2 3 4 5 6 7 8 9]

in c#

I was doing

int[,] array2D = new int[,] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
int[] array1D = new int[9];
int ci=0;

 for (int i = 0; i < 3; i++)
 {
      for (int j = 0; j < 3; j++)
      {
            array1D[ci++] = array2D[i, j]);
      }
 }
edgarmtze
  • 24,683
  • 80
  • 235
  • 386
  • 2
    [This][1] seems to be the same questions, answered. [1]: http://stackoverflow.com/questions/2569279/how-to-flatten-2d-array-to-1d-array – Oleksi Jan 25 '12 at 05:05
  • 1
    @Olexsi it's not quite the same; this question uses a proper 2-dimensional array, while the one you linked to is asking about a 2-dimensional jagged array. – phoog Jan 25 '12 at 06:26

4 Answers4

8

LINQ makes this trivial.

int[,] array2d = ...;
var array1d = array2d.Cast<int>().ToArray();

Otherwise, your way is adequate but could be generalized:

int[,] array2d = ...;
var rows = array2d.GetLength(0);
var cols = array2d.GetLength(1);
var array1d = new int[rows * cols];
var current = 0;
for (int i = 0; i < rows; i++)
{
    for (int j = 0; j < cols; j++)
    {
        array1d[current++] = array2d[i, j];
    }
}

Or even:

int[,] array2d = ...;
var array1d = new int[array2d.GetLength(0) * array2d.GetLength(1)];
var current = 0;
foreach (var value in array2d)
{
    array1d[current++] = value;
}
Jeff Mercado
  • 129,526
  • 32
  • 251
  • 272
  • Which one is faster and less resource consumption ? – kbvishnu Jan 25 '12 at 05:38
  • I don't know, I haven't profiled it myself. But I'd suspect the third version would be the fastest. – Jeff Mercado Jan 25 '12 at 05:39
  • what abt LINQ ? It will fast I hope – kbvishnu Jan 25 '12 at 05:48
  • 2
    It appears the second one is actually fastest with the third coming in pretty close within ~120ms. The first version coming in the slowest 55 times slower than the fastest in my tests. One million iterations converting a 10x10 matrix. `time ms (ticks)`: `27349ms (75675449)`, `491ms (1359839)`, `614ms (1700921)`. – Jeff Mercado Jan 25 '12 at 05:53
  • @JeffMercado in the first example, there's no need to call .Cast() as the elements are already ints. – phoog Jan 25 '12 at 06:11
  • @phoog: But multidimensional arrays only implement the non-generic `IEnumerable` interface. The cast is necessary to be able to use LINQ there. – Jeff Mercado Jan 25 '12 at 06:13
  • aha, I didn't know that. Thanks for the information. That also explains the poorer performance because of the boxing. – phoog Jan 25 '12 at 06:15
1

Alternate solution using Buffer.BlockCopy:

int[,] array2D = new int[,] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
int[] array1D = new int[ array2D.Length ];
Buffer.BlockCopy(array2D, 0, array1D, 0, array1D.Length * sizeof(int));
sakra
  • 62,199
  • 16
  • 168
  • 151
1

As Jeff said, LINQ makes this trivial. OfType<>() should generally be a little faster than Cast<> though:

array1D = array2D.OfType<int>().ToArray();

The implementation of OfType<> however will still suffer from boxing/unboxing penalties, as @phoog mentioned.

Just for the fun of it, if you want a fast LINQ-based solution (avoiding the cost of boxing) you could use this small extension method:

static class LinqEx
{
    public static IEnumerable<T> Flatten<T>(this T[,] matrix)
    {
        foreach (var item in matrix) yield return item;
    }
}

Or this, based on Jeff's 2nd solution:

    public static IEnumerable<T> Flatten<T>(this T[,] matrix)
    {
        var rows = matrix.GetLength(0);
        var cols = matrix.GetLength(1);
        for (var i = 0; i < rows;i++ )
        {
            for (var j = 0; j < cols; j++ )
                yield return matrix[i, j];
        }
    }

usage:

 int[,] array2D = new int[,] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };  
 int[] array1D = array2D.Flatten().ToArray();

I didn't fully profile this but I expect this will get you much better performance than the built-in options based on LINQ/IEnumerable. Jeff's second solution will however always be the fasted, it seems.

jeroenh
  • 26,362
  • 10
  • 73
  • 104
0

You're always better off allocating the complete result array in one hit, then copying the data in.

You should find the total size like this;

var size = arrays.Sum(a=> a.Length);
var result = new int[size];

And then copy the arrays using Array.CopyTo, instead of looping yourself;

var cursor = 0;
foreach(var a in arrays) {
   a.CopyTo(result, cursor);
   cursor += a.Length;    
}

Array.CopyTo will be faster than your own loop; at least, not slower. It will probably use C's memcpy function internally to do a low-level block copy. This is as efficient as you can be.

Steve Cooper
  • 20,542
  • 15
  • 71
  • 88