如何将2D数组中的一行值复制到1D数组中?

19
我们有以下对象。
int [,] oGridCells;

这个只会用于固定的第一个索引。

int iIndex = 5;
for (int iLoop = 0; iLoop < iUpperBound; iLoop++)
{
  //Get the value from the 2D array
  iValue = oGridCells[iIndex, iLoop];

  //Do something with iValue
}

在.NET中,是否有一种方法可以将固定第一个索引处的值转换为单维数组(而不是通过循环值来实现)?

如果数组仅被循环一次,我怀疑这样做不会加快代码速度(并且可能会使其变慢)。但是,如果数组被大量操作,那么与多维数组相比,单维数组将更加高效。

我提出这个问题的主要原因是想看看是否可以完成,以及如何完成,而不是将其用于生产代码。

7个回答

36
以下代码演示了将一个二维数组中的16个字节(4个整数)复制到一个一维数组中。
int[,] oGridCells = {{1, 2}, {3, 4}};
int[] oResult = new int[4];
System.Buffer.BlockCopy(oGridCells, 0, oResult, 0, 16);
您还可以通过提供正确的字节偏移量有选择地复制数组中的某一行。此示例复制了一个3行2D数组的中间行。
int[,] oGridCells = {{1, 2}, {3, 4}, {5, 6}};
int[] oResult = new int[2];
System.Buffer.BlockCopy(oGridCells, 8, oResult, 0, 8);

这样做无法达到所需的结果,因为它会复制所有值,而不仅仅是第一个索引0或1的值。 - stevehipwell
我添加了一个示例来复制1行。 - BlueMonkMN
要使用此方法,您需要计算数组的边界以获得偏移量。对于多维数组来说这是不高效的。但它看起来似乎可行。 - stevehipwell
有什么效率低下的问题吗?元素的大小乘以每行元素的数量再乘以行索引就是偏移量。如果您的行索引不变,只需计算一次偏移量即可。 - BlueMonkMN
同意,我想你必须调用低效的方法来找到上限,才能像问题源代码中那样循环遍历元素。不过你的代码看起来不太对,因为你的数组是int [2, 3],所以数据的一行应该是int [3],位于索引0或1。 - stevehipwell
显示剩余3条评论

4
你可以试试这个:

 int[,] twoD = new int[2,2];
 twoD[0, 0] = 1;
 twoD[0, 1] = 2;
 twoD[1, 0] = 3;
 twoD[1, 1] = 4;

 int[] result = twoD.Cast<int>().Select(c => c).ToArray();

结果将是一个带有数据的整数数组:
1, 2, 3, 4

为什么要使用Select? - Artem Popov
2
如果你想的话,可以删除select,这也没问题。 - Willy David Jr

3

编辑:

我意识到有一种方法!虽然可能不值得。使用不安全的代码。下面是完整示例,展示了两种方法,其中包括不安全的方式:

public class MultiSingleUnsafe
{
    public static unsafe void Main(String[] a)
    {
    int rowCount = 6;
    int iUpperBound = 10;
    int [,] oGridCells = new int[rowCount, iUpperBound];

    int iIndex = rowCount - 2; // Pick a row.

    for(int i = 0; i < iUpperBound; i++)
    {
        oGridCells[iIndex, i] = i;
    }

    for (int iLoop = 0; iLoop < iUpperBound; iLoop++)
    {
        //Get the value from the 2D array
        int iValue = oGridCells[iIndex, iLoop];
        Console.WriteLine("Multi-dim array access iValue: " + iValue);
        //Do something with iValue
    }
    
    fixed(int *lastRow = &(oGridCells[iIndex,0]))
    {   
        for (int iLoop = 0; iLoop < iUpperBound; iLoop++)
        {
        int iValue = lastRow[iLoop];
        Console.WriteLine("Pointer access iValue: " + iValue);
        }
    }
    }
}

我不知道在C#中如何将多维数组转换为一维数组。当然,你可以创建一个新的一维数组并将其复制到其中。但是,即使您多次循环遍历值,我认为这也不会带来性能上的好处。正如Daren所说,内部所有操作都是指针运算。如果您想确定,请进行性能分析。


1
我不是在寻找一个强制转换,我知道那是不可能的。 - stevehipwell

1

如果这是可能的,我会感到惊讶:我敢打赌oGridCells[iIndex, iLoop]只是一种简写方式(在MSIL内部),实际上等同于oGridCells[iIndex * iLoop],而多维数组只是语法糖。

回答你的问题:不行。你必须循环这些值。


它们与单维数组不同。零索引的单维数组是向量类型,而所有其他数组都是数组类型。因此不是语法糖。 - stevehipwell

1

你无法获取每个数组的引用。但是,你可以使用锯齿数组


同意,如果我重新编写代码(这不是一个选项),我会使用锯齿数组。特别是因为锯齿数组比多维数组具有更好的性能。 - stevehipwell

1

但是,如果数组被大量操作,则单维数组比多维数组更有效率。

去年夏天,我对此进行了一些分析,惊讶地发现2D和1D数组之间的性能差异不大。

我没有测试过交错数组的性能。


它们是不同的类型,某些操作(例如测试长度)可能会慢得多。而锯齿数组则提供了两者兼具的最佳选择。 - stevehipwell

0

看起来在.NET Core和.NET 5+上使用Span.CopyTo比使用Buffer.BlockCopy(如@BlueMonkMN的答案中所述)要快大约30%。

public static T[] CopyToArray<T>(ref T start, int length)
{
    var target = new T[length];
    var span = MemoryMarshal.CreateReadOnlySpan(ref start, length);
    span.CopyTo(target.AsSpan());
    return target;
}

你可以像这样使用它(显然要确保不超出数组范围,因为没有边界检查)。
var singleArray = CopyToArray(ref oGridCells[0, 0], oGridCells.Length);

dotnetfiddle

BenchmarkDotNet结果

方法 平均值 误差 标准偏差
BlueMonkMN 18.84 ns 0.220 ns 0.206 ns
Charlieface 12.31 ns 0.196 ns 0.183 ns

比什么更快? - IS4
与被接受的答案相比,很抱歉认为这在基准测试中是显而易见的。 - Charlieface

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接