【问题标题】:How to resize multidimensional (2D) array in C#?如何在 C# 中调整多维(2D)数组的大小?
【发布时间】:2011-06-30 18:43:30
【问题描述】:

我尝试了以下方法,但它只是返回一个搞砸的数组。

    T[,] ResizeArray<T>(T[,] original, int rows, int cols)
    {
        var newArray = new T[rows,cols];
        Array.Copy(original, newArray, original.Length);
        return newArray;
    }

【问题讨论】:

    标签: c# arrays


    【解决方案1】:

    数组类中的大多数方法只适用于一维数组,因此您必须手动执行复制:

    T[,] ResizeArray<T>(T[,] original, int rows, int cols)
    {
        var newArray = new T[rows,cols];
        int minRows = Math.Min(rows, original.GetLength(0));
        int minCols = Math.Min(cols, original.GetLength(1));
        for(int i = 0; i < minRows; i++)
            for(int j = 0; j < minCols; j++)
               newArray[i, j] = original[i, j];
        return newArray;
    }
    

    要了解为什么它不适用于Array.Copy,您需要考虑内存中多维数组的布局。数组项真正存储为二维数组,它们是连续存储的,一行接一行。所以这个数组:

    { { 1, 2, 3 },
      { 4, 5, 6 } }
    

    实际上是这样排列在内存中的:{ 1, 2, 3, 4, 5, 6 }

    现在,假设您想再添加一行和一列,这样数组看起来像这样:

    { { 1, 2, 3, 0 },
      { 4, 5, 6, 0 },
      { 0, 0, 0, 0 } }
    

    现在内存中的布局如下:{ 1, 2, 3, 0, 4, 5, 6, 0, 0, 0, 0, 0 }

    Array.Copy 将所有数组视为一维。 MSDN 说:

    在多维数组之间复制时,数组的行为类似于一个长的一维数组,其中行(或列)在概念上是首尾相连的

    因此,当您尝试将原始数组复制到新数组时,它只是将一个内存位置复制到另一个内存位置,从而以一维表示形式给出:

    { 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }.

    如果将其转换为二维表示,则会得到以下结果:

    { { 1, 2, 3, 4 },
      { 5, 6, 0, 0 },
      { 0, 0, 0, 0 } }
    

    这就是为什么您会得到一个搞砸的数组...请注意,如果您更改行数而不是列数,它将起作用。

    【讨论】:

    • 如果(rows&lt;oldRows)||(cols&lt;oldCols) 则抛出。需要在循环中使用Min(rows,oldRows)
    • @CodeInChaos,你说得对,我只考虑了数组变大的情况......我会修复它
    • 注意:如果只有数组的第一个维度发生变化,那么在行序数组的情况下内存连续性不是问题。事实上,在这种情况下,应该可以使用单个副本调整大小。只有在第一次更改之外的其他维度存在问题时。
    【解决方案2】:

    这结合了 Thomas 和 Manuel 的答案,并提供了 Array.Copy 的性能优势以及增加和减少数组大小的能力。

        protected T[,] ResizeArray<T>(T[,] original, int x, int y)
        {
            T[,] newArray = new T[x, y];
            int minX = Math.Min(original.GetLength(0), newArray.GetLength(0));
            int minY = Math.Min(original.GetLength(1), newArray.GetLength(1));
    
            for (int i = 0; i < minY; ++i)
                Array.Copy(original, i * original.GetLength(0), newArray, i * newArray.GetLength(0), minX);
    
            return newArray;
        }
    

    请注意,数组的 x 和 y 轴取决于您自己的实现,您可能需要左右切换 ​​0 和 1 才能达到预期的效果。

    【讨论】:

      【解决方案3】:

      谢谢 Thomas,您的解释很有帮助,但您实施的解决方案太慢了。我对其进行了修改以充分利用 Array.Copy。

          void ResizeArray<T>(ref T[,] original, int newCoNum, int newRoNum)
          {
              var newArray = new T[newCoNum,newRoNum];
              int columnCount = original.GetLength(1);
              int columnCount2 = newRoNum;
              int columns = original.GetUpperBound(0);
              for (int co = 0; co <= columns; co++)
                  Array.Copy(original, co * columnCount, newArray, co * columnCount2, columnCount);
              original = newArray;
          }
      

      这里我假设行数多于列数,因此我将数组构造为 [columns, rows]。这样我就可以一次性在整个列上使用 Array.Copy(比一次一个单元格快得多)。

      它只能增加数组的大小,但也可以调整它以减小大小。

      【讨论】:

        【解决方案4】:

        对于多维数组的通用调整大小:

        public static class ArrayExtentions {
            public static Array ResizeArray(this Array arr, int[] newSizes) {
                if (newSizes.Length != arr.Rank) {
                    throw new ArgumentException("arr must have the same number of dimensions as there are elements in newSizes", "newSizes");
                }
        
                var temp = Array.CreateInstance(arr.GetType().GetElementType(), newSizes);
                var sizesToCopy = new int[newSizes.Length];
                for (var i = 0; i < sizesToCopy.Length; i++) {
                    sizesToCopy[i] = Math.Min(newSizes[i], arr.GetLength(i));
                }
        
                var currentPositions = new int[sizesToCopy.Length];
                CopyArray(arr, temp, sizesToCopy, currentPositions, 0);
        
                return temp;
            }
        
            private static void CopyArray(Array arr, Array temp, int[] sizesToCopy, int[] currentPositions, int dimmension) {
                if (arr.Rank - 1 == dimmension) {
                    //Copy this Array
                    for (var i = 0; i < sizesToCopy[dimmension]; i++) {
                        currentPositions[dimmension] = i;
                        temp.SetValue(arr.GetValue(currentPositions), currentPositions);
                    }
                } else {
                    //Recursion one dimmension higher
                    for (var i = 0; i < sizesToCopy[dimmension]; i++) {
                        currentPositions[dimmension] = i;
                        CopyArray(arr, temp, sizesToCopy, currentPositions, dimmension + 1);
                    }
                }
            }
        }
        

        【讨论】:

          【解决方案5】:

          我一直在寻找类似的东西,但它可以有效地让我从两端“填充”二维数组,并且还能够减少它。

          我运行了一个非常简单的测试: 数组是字符串 [1000,1000],我的机器每次调整大小的平均时间是 44 毫秒。 每次调整大小都会将所有边的填充增加或减少 1,因此复制了数组中的所有数据。这种性能影响超出了我的要求。

          public static void ResizeArray<T>(
              ref T[,] array, int padLeft, int padRight, int padTop, int padBottom)
          {
              int ow = array.GetLength(0);
              int oh = array.GetLength(1);
              int nw = ow + padLeft + padRight;
              int nh = oh + padTop + padBottom;
          
              int x0 = padLeft;
              int y0 = padTop;
              int x1 = x0 + ow - 1;
              int y1 = y0 + oh - 1;
              int u0 = -x0;
              int v0 = -y0;
          
              if (x0 < 0) x0 = 0;
              if (y0 < 0) y0 = 0;
              if (x1 >= nw) x1 = nw - 1;
              if (y1 >= nh) y1 = nh - 1;
          
              T[,] nArr = new T[nw, nh];
              for (int y = y0; y <= y1; y++)
              {
                  for (int x = x0; x <= x1; x++)
                  {
                      nArr[x, y] = array[u0 + x, v0 + y];
                  }
              }
              array = nArr;
          }
          

          padLeft、padRight、padTop、padBottom 可以是负数或正数。如果传入全 0,则生成的数组将与源数组相同。

          这对于想要围绕数组“滚动”元素的任何人都特别有用。

          希望它对某人有用!

          【讨论】:

            【解决方案6】:

            这建立在 Manuel 的回答的基础上,还允许对复制的数据应用偏移量(例如,将源数组复制到目标数组的中心,而不是复制到 [0, 0]):

            public static T[,] ResizeArray<T>(T[,] original, int newWidth, int newHeight, int offsetX = 0, int offsetY = 0)
            {
                T[,] newArray = new T[newWidth, newHeight];
                int width = original.GetLength(0);
                int height = original.GetLength(1);
                for (int x = 0; x < width; x++) {
                    Array.Copy(original, x * height, newArray, (x + offsetX) * newHeight + offsetY, height);
                }
            
                return newArray;
            }
            

            【讨论】:

              【解决方案7】:

              我真的很喜欢 Stephen Tierney 在他人工作基础上的回答。 正如斯蒂芬指出的那样,x/y 取决于解释。

              我已经稍微重构了它以使用 m*n 大小和 ij 坐标索引,这可能是矩阵最常见的表示法(参考维基百科https://en.wikipedia.org/wiki/Matrix_(mathematics))。

              在这个符号中,

              • 'm'是行数
              • 'n' 是列数
              • 'i' 是第一个坐标,也就是行索引(向下增加)
              public static T[,] Resize2D<T>(this T[,] original, int m, int n)
              {
              
                  T[,] newArray = new T[m, n];
                  int mMin = Math.Min(original.GetLength(0), newArray.GetLength(0));
                  int nMin = Math.Min(original.GetLength(1), newArray.GetLength(1));
              
                  for (int i = 0; i < mMin; i++)
                      Array.Copy(original, i * original.GetLength(1), newArray, i * newArray.GetLength(1), nMin);
              
                  return newArray;
              }
              

              【讨论】:

              • 在 VB.NET 的 Utils.CopyArray 的反射中,有一个非常灵活的数组管理器。
              猜你喜欢
              • 2016-06-16
              • 2017-01-05
              • 1970-01-01
              • 2011-04-27
              • 2018-09-21
              • 1970-01-01
              • 1970-01-01
              • 2020-11-15
              • 1970-01-01
              相关资源
              最近更新 更多