Подтвердить что ты не робот

Как изменить размер многомерного (2D) массива в С#?

Я попробовал следующее, но он просто возвращает прикрученный массив.

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

Ответ 1

Спасибо, Томас, ваше объяснение было очень полезно, но ваше реализованное решение было слишком медленным. Я изменил его, чтобы поместить 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;
    }

Здесь я предполагаю, что больше строк, чем столбцов, поэтому я структурировал массив как [столбцы, строки]. Таким образом, я использую Array.Copy для всего столбца за один снимок (намного быстрее, чем одна ячейка за раз).

Он работает только для увеличения размера массива, но его можно, вероятно, изменить, чтобы уменьшить размер.

Ответ 2

Большинство методов в классе массива работают только с одномерными массивами, поэтому вам нужно выполнить копию вручную:

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

Вот почему вы получаете запутанный массив... Обратите внимание, что он будет работать с собственностью, если вы измените количество строк, но не количество столбцов.

Ответ 3

Это объединяет ответы Томаса и Мануэля и дает преимущество в производительности 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 вашего массива соответствуют вашей собственной реализации, и вам может потребоваться переключить 0s и 1s для достижения желаемого эффекта.

Ответ 4

Я искал что-то вроде этого, но кое-что, что позволило бы мне "набить" 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

И для общего изменения размеров многомерных массивов:

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