如何在C#中调整多维(2D)数组的大小?

时间:2011-06-30 18:43:30

标签: c# arrays

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

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

7 个答案:

答案 0 :(得分:35)

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

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

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

答案 1 :(得分:13)

这结合了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来实现所需的效果。

答案 2 :(得分:9)

谢谢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(一次比一个单元快得多)。

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

答案 3 :(得分:3)

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

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

答案 4 :(得分:1)

我一直在寻找类似这样的东西,但是有效地让我从两端“填充”2D阵列,并且还能够减少它。

我做了一个非常简单的测试: 数组是字符串[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,则生成的数组将与源数组相同。

对于想要在阵列周围“滚动”元素的人来说,这可能特别有用。

希望它对某人有用!

答案 5 :(得分:1)

这是基于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;
}

答案 6 :(得分:1)

我真的很喜欢斯蒂芬蒂尔尼在其他人的工作基础上的回答。 正如斯蒂芬指出的那样,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;
}