'How to resize multidimensional (2D) array in C#?
I tried the following but it just returns a screwed up array.
T[,] ResizeArray<T>(T[,] original, int rows, int cols)
{
var newArray = new T[rows,cols];
Array.Copy(original, newArray, original.Length);
return newArray;
}
Solution 1:[1]
Thank you Thomas, your explanation was very helpful but your implemented solution is too slow. I modified it to put Array.Copy to good use.
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;
}
Here I'm assuming that there are more rows than columns so I structured the array as [columns, rows]. That way I use Array.Copy on an entire column in one shot (much faster than one cell a time).
It only works to increment the size of the array but it can probably be tweaked to reduce the size too.
Solution 2:[2]
Most methods in the array class only work with one-dimensional arrays, so you have to perform the copy manually:
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;
}
To understand why it doesn't work with Array.Copy
, you need to consider the layout of a multidimensional array in memory. The array items are not really stored as a bidimensional array, they're stored contiguously, row after row. So this array:
{ { 1, 2, 3 },
{ 4, 5, 6 } }
Is actually arranged in memory like that: { 1, 2, 3, 4, 5, 6 }
Now, assume you want to add one more row and one more column, so that the array looks like this:
{ { 1, 2, 3, 0 },
{ 4, 5, 6, 0 },
{ 0, 0, 0, 0 } }
The layout in memory would now be as follows: { 1, 2, 3, 0, 4, 5, 6, 0, 0, 0, 0, 0 }
But Array.Copy
treats all arrays as one-dimensional. MSDN says:
When copying between multidimensional arrays, the array behaves like a long one-dimensional array, where the rows (or columns) are conceptually laid end to end
So when you try to copy the original array to the new one, it just copies one memory location to the other, which gives, in one-dimensional representation:
{ 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0 }
.
If you convert that to a two-dimensional representation, you get the following:
{ { 1, 2, 3, 4 },
{ 5, 6, 0, 0 },
{ 0, 0, 0, 0 } }
This is why you're getting a screwed up array... Note that it would work property if you changed the number of rows, but not the number of columns.
Solution 3:[3]
This combines Thomas and Manuel's answers and provides the performance benefit of Array.Copy and the ability to both increase and decrease the size of the array.
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;
}
Please note that the x and y axis of your array is up to your own implementation and you may need to switch the 0s and 1s around to achieve the desired effect.
Solution 4:[4]
And for generic resizing of multi dimensional arrays:
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);
}
}
}
}
Solution 5:[5]
I was looking for something like this, but something that would effectively let me "pad" a 2D array from both ends, and also have the ability to reduce it.
I've run a very simple test: The array was string[1000,1000], average time for my machine was 44ms per resize. The resize increased or decreased padding on all sides by 1 each time, so all data in the array was copied. This performance hit was more than acceptable for my requirements.
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 can be negative or positive. If you pass in all 0s, the array generated will be identical to the source array.
This could be particularly useful for anyone who wants to "scroll" their elements around their array.
Hope it's of use to someone!
Solution 6:[6]
This builds on Manuel's answer to also allow to apply an offset to the copied data (e.g. to copy the source array to the center of the target array, instead of to [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;
}
Solution 7:[7]
I really like Stephen Tierney's answer building upon the work of others. As Stephen notes the x/y is up to interpretation.
I have refactored it slightly to use m*n sizes & ij coordinate indexing that is probably the most common notation for matrices (wikipedia for reference https://en.wikipedia.org/wiki/Matrix_(mathematics)).
In this notation,
- 'm' is the number of rows
- 'n' is the number of columns
- 'i' is the first coordinate, aka row index (increases as you go down)
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;
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 | |
Solution 4 | Markus |
Solution 5 | Graeme Job |
Solution 6 | |
Solution 7 | Gav |