在 C# 中对二维数组进行零填充
Zero padding a 2D array in C#
我目前在使用零填充二维数组时遇到问题。我想将我的数组中的当前数据传输到一个新数组,它是完全相同的数组,但周围有一个 0 的边框。
示例:
|1 2 3|
|4 5 6|
|7 8 9|
应该变成
|0 0 0 0 0|
|0 1 2 3 0|
|0 4 5 6 0|
|0 7 8 9 0|
|0 0 0 0 0|
int[,] Array = new int[,] { { 1, 2, 3 }, { 3, 4, 5 }, { 6, 7, 8 } };
int[,] ArrayZeroPad = new int[Array.GetLength(0) + 2, Array.GetLength(1) + 2];
for (int y = 0; y < Array.GetLength(1); y++)
{
for (int x = 0; x < ArrayZeroPad.GetLength(0); x++)
{
if (y == 0)
{ ArrayZeroPad[y, x] = 0; }
else if (y == ArrayZeroPad.GetLength(1))
{ ArrayZeroPad[y, x] = 0; }
else if (x == 0)
{
ArrayZeroPad[y, x] = 0;
}
else if (x == ArrayZeroPad.GetLength(0))
{ ArrayZeroPad[y, x] = 0; }
else ArrayZeroPad[y, x] = Array[y, x];
}
}
for (int y = 0; y < ArrayZeroPad.GetLength(1); y++)
{
Console.WriteLine();
for (int x = 0; x < ArrayZeroPad.GetLength(0); x++)
{ Console.Write(ArrayZeroPad[y, x]); }
Console.ReadLine();
}
}
这就是我到目前为止所遇到的问题,但我一直卡在越界错误中,有没有人可以通过一些解释为我解决这个问题?
亲切的问候,
D.
您似乎混淆了尺寸 - Array.GetLength(0)
是访问中的第一个 Array[i, j]
而 Array.GetLength(1)
是第二个。您还可以通过扫描 Array
元素并将目标索引调整一个来简化复制,您不需要将其他元素显式设置为 0
因为它会为您完成(除非您使用 stackalloc
和 skipping local init 但我非常怀疑情况是否如此):
var length0 = Array.GetLength(0);
var length1 = Array.GetLength(1);
for (int i = 0; i < length0; i++)
{
for (int j = 0; j < length1; j++)
{
ArrayZeroPad[i + 1, j + 1] = Array[i, j];
}
}
并且在“打印”方法中 - y
应该是第一个维度,x
- 第二个维度:
var length = ArrayZeroPad.GetLength(0);
for (int y = 0; y < length; y++)
{
Console.WriteLine();
var i = ArrayZeroPad.GetLength(1);
for (int x = 0; x < i; x++)
{
Console.Write(ArrayZeroPad[y, x]);
}
Console.ReadLine();
}
int[,] Array = new int[,] { { 1, 2, 3 }, { 3, 4, 5 }, { 6, 7, 8 } };
int[,] ArrayZeroPad = new int[Array.GetLength(0) + 2, Array.GetLength(1) + 2];
for (int x = 0; x < ArrayZeroPad.GetLength(0); x++)
{
for (int y = 0; y < ArrayZeroPad.GetLength(0); y++)
{
//First row and last row
if (x == 0 || x == ArrayZeroPad.GetLength(0) - 1)
ArrayZeroPad[x, y] = 0;
else
{
//Fist column and last column
if (y == 0 || y == ArrayZeroPad.GetLength(0) - 1)
ArrayZeroPad[x, y] = 0;
else
{
//Content
ArrayZeroPad[x, y] = Array[x-1, y-1];
}
}
}
}
您也可以使用 Array.Copy()
解决此问题。如果您需要最高性能并且数组足够大,那么这可能比显式复制每个元素更快:
public static int[,] Pad(int[,] input)
{
int h = input.GetLength(0);
int w = input.GetLength(1);
var output = new int[h+2, w+2];
for (int r = 0; r < h; ++r)
{
Array.Copy(input, r*w, output, (r+1)*(w+2)+1, w);
}
return output;
}
(r+1)*(w+2)+1
需要一些解释。 Array.Copy()
将二维数组视为线性一维数组,您必须将副本的目标偏移量指定为距一维数组开头的偏移量(按行优先顺序)。
由于 w
是输入数组的宽度,而 r
是输入数组的当前行,因此当前输入行的副本目标将是输出行号, (r+1)
乘以输出行宽 (w+2)
,加上 1
以占输出数组中 0
的左侧列。
使用 Buffer.BlockCopy()
(对字节进行操作)可能会更快:
public static int[,] Pad(int[,] input)
{
int h = input.GetLength(0);
int w = input.GetLength(1);
var output = new int[h+2, w+2];
for (int r = 0; r < h; ++r)
{
Buffer.BlockCopy(input, r*w*sizeof(int), output, ((r+1)*(w+2)+1)*sizeof(int), w*sizeof(int));
}
return output;
}
一如既往,只有在性能至关重要的情况下才值得担心,即便如此,也只有在您对代码进行基准测试以验证它实际上 更快之后才值得担心。
这不是您要问的(我认为完全不同的替代方案会很有趣)。
这是一个 No-Copy 版本,适用于任何类型、任何大小的数组。如果原始数组非常大(因为它不需要副本),这是合适的。
它使用二维索引器,returns 边缘项目的默认值 T(零或空),并使用原始数组(具有索引偏移量)用于非边缘值:
public class ZeroPadArray <T>
{
private readonly T[,] _initArray;
public ZeroPadArray(T[,] arrayToPad)
{
_initArray = arrayToPad;
}
public T this[int i, int j]
{
get
{
if (i < 0 || i > _initArray.GetLength(0) + 1)
{
throw new ArgumentOutOfRangeException(nameof(i),
$@"Index {nameof(i)} must be between 0 and the width of the padded array");
}
if (j < 0 || j > _initArray.GetLength(1) + 1)
{
throw new ArgumentOutOfRangeException(nameof(j),
$@"Index {nameof(j)} must be between 0 and the width of the padded array");
}
if (i == 0 || j == 0)
{
return default(T);
}
if (i == _initArray.GetLength(0) + 1)
{
return default(T);
}
if (j == _initArray.GetLength(1) + 1)
{
return default(T);
}
//otherwise, just offset into the original array
return _initArray[i - 1, j - 1];
}
}
}
我刚刚通过一些 Debug.Assert
调用对其进行了测试。测试覆盖率很低,但足以说“这可能有效”:
int[,] array = new int[,] { { 1, 2, 3 }, { 11, 12, 13 }, { 21, 22, 23 } };
var paddedArray = new ZeroPadArray<int>(array);
Debug.Assert(paddedArray[0, 0] == 0);
Debug.Assert(paddedArray[4,4] == 0);
Debug.Assert(paddedArray[2,3] == 13);
最后,为了好玩,我添加了一个不错的小 hack 来创建这些东西需要更少的输入。当你调用一个方法时,编译器通常能够从方法参数中推断出对象的泛型类型。这不适用于构造函数。这就是为什么你需要指定 new ZeroPadArray<int>(array)
即使 array
显然是 int
.
的数组
解决此问题的方法是创建第二个非泛型 class,用作创建事物的静态工厂。类似于:
public static class ZeroPadArray
{
public static ZeroPadArray<T> Create<T>(T[,] arrayToPad)
{
return new ZeroPadArray<T>(arrayToPad);
}
}
现在,不用输入:
var paddedArray = new ZeroPadArray<int>(array);
您可以输入:
var paddedArray = ZeroPadArray.Create(array);
为您节省了两个字符的输入(但是,您需要承认输入 <int>
令人沮丧)。
我目前在使用零填充二维数组时遇到问题。我想将我的数组中的当前数据传输到一个新数组,它是完全相同的数组,但周围有一个 0 的边框。 示例:
|1 2 3|
|4 5 6|
|7 8 9|
应该变成
|0 0 0 0 0|
|0 1 2 3 0|
|0 4 5 6 0|
|0 7 8 9 0|
|0 0 0 0 0|
int[,] Array = new int[,] { { 1, 2, 3 }, { 3, 4, 5 }, { 6, 7, 8 } };
int[,] ArrayZeroPad = new int[Array.GetLength(0) + 2, Array.GetLength(1) + 2];
for (int y = 0; y < Array.GetLength(1); y++)
{
for (int x = 0; x < ArrayZeroPad.GetLength(0); x++)
{
if (y == 0)
{ ArrayZeroPad[y, x] = 0; }
else if (y == ArrayZeroPad.GetLength(1))
{ ArrayZeroPad[y, x] = 0; }
else if (x == 0)
{
ArrayZeroPad[y, x] = 0;
}
else if (x == ArrayZeroPad.GetLength(0))
{ ArrayZeroPad[y, x] = 0; }
else ArrayZeroPad[y, x] = Array[y, x];
}
}
for (int y = 0; y < ArrayZeroPad.GetLength(1); y++)
{
Console.WriteLine();
for (int x = 0; x < ArrayZeroPad.GetLength(0); x++)
{ Console.Write(ArrayZeroPad[y, x]); }
Console.ReadLine();
}
}
这就是我到目前为止所遇到的问题,但我一直卡在越界错误中,有没有人可以通过一些解释为我解决这个问题?
亲切的问候, D.
您似乎混淆了尺寸 - Array.GetLength(0)
是访问中的第一个 Array[i, j]
而 Array.GetLength(1)
是第二个。您还可以通过扫描 Array
元素并将目标索引调整一个来简化复制,您不需要将其他元素显式设置为 0
因为它会为您完成(除非您使用 stackalloc
和 skipping local init 但我非常怀疑情况是否如此):
var length0 = Array.GetLength(0);
var length1 = Array.GetLength(1);
for (int i = 0; i < length0; i++)
{
for (int j = 0; j < length1; j++)
{
ArrayZeroPad[i + 1, j + 1] = Array[i, j];
}
}
并且在“打印”方法中 - y
应该是第一个维度,x
- 第二个维度:
var length = ArrayZeroPad.GetLength(0);
for (int y = 0; y < length; y++)
{
Console.WriteLine();
var i = ArrayZeroPad.GetLength(1);
for (int x = 0; x < i; x++)
{
Console.Write(ArrayZeroPad[y, x]);
}
Console.ReadLine();
}
int[,] Array = new int[,] { { 1, 2, 3 }, { 3, 4, 5 }, { 6, 7, 8 } };
int[,] ArrayZeroPad = new int[Array.GetLength(0) + 2, Array.GetLength(1) + 2];
for (int x = 0; x < ArrayZeroPad.GetLength(0); x++)
{
for (int y = 0; y < ArrayZeroPad.GetLength(0); y++)
{
//First row and last row
if (x == 0 || x == ArrayZeroPad.GetLength(0) - 1)
ArrayZeroPad[x, y] = 0;
else
{
//Fist column and last column
if (y == 0 || y == ArrayZeroPad.GetLength(0) - 1)
ArrayZeroPad[x, y] = 0;
else
{
//Content
ArrayZeroPad[x, y] = Array[x-1, y-1];
}
}
}
}
您也可以使用 Array.Copy()
解决此问题。如果您需要最高性能并且数组足够大,那么这可能比显式复制每个元素更快:
public static int[,] Pad(int[,] input)
{
int h = input.GetLength(0);
int w = input.GetLength(1);
var output = new int[h+2, w+2];
for (int r = 0; r < h; ++r)
{
Array.Copy(input, r*w, output, (r+1)*(w+2)+1, w);
}
return output;
}
(r+1)*(w+2)+1
需要一些解释。 Array.Copy()
将二维数组视为线性一维数组,您必须将副本的目标偏移量指定为距一维数组开头的偏移量(按行优先顺序)。
由于 w
是输入数组的宽度,而 r
是输入数组的当前行,因此当前输入行的副本目标将是输出行号, (r+1)
乘以输出行宽 (w+2)
,加上 1
以占输出数组中 0
的左侧列。
使用 Buffer.BlockCopy()
(对字节进行操作)可能会更快:
public static int[,] Pad(int[,] input)
{
int h = input.GetLength(0);
int w = input.GetLength(1);
var output = new int[h+2, w+2];
for (int r = 0; r < h; ++r)
{
Buffer.BlockCopy(input, r*w*sizeof(int), output, ((r+1)*(w+2)+1)*sizeof(int), w*sizeof(int));
}
return output;
}
一如既往,只有在性能至关重要的情况下才值得担心,即便如此,也只有在您对代码进行基准测试以验证它实际上 更快之后才值得担心。
这不是您要问的(我认为完全不同的替代方案会很有趣)。
这是一个 No-Copy 版本,适用于任何类型、任何大小的数组。如果原始数组非常大(因为它不需要副本),这是合适的。
它使用二维索引器,returns 边缘项目的默认值 T(零或空),并使用原始数组(具有索引偏移量)用于非边缘值:
public class ZeroPadArray <T>
{
private readonly T[,] _initArray;
public ZeroPadArray(T[,] arrayToPad)
{
_initArray = arrayToPad;
}
public T this[int i, int j]
{
get
{
if (i < 0 || i > _initArray.GetLength(0) + 1)
{
throw new ArgumentOutOfRangeException(nameof(i),
$@"Index {nameof(i)} must be between 0 and the width of the padded array");
}
if (j < 0 || j > _initArray.GetLength(1) + 1)
{
throw new ArgumentOutOfRangeException(nameof(j),
$@"Index {nameof(j)} must be between 0 and the width of the padded array");
}
if (i == 0 || j == 0)
{
return default(T);
}
if (i == _initArray.GetLength(0) + 1)
{
return default(T);
}
if (j == _initArray.GetLength(1) + 1)
{
return default(T);
}
//otherwise, just offset into the original array
return _initArray[i - 1, j - 1];
}
}
}
我刚刚通过一些 Debug.Assert
调用对其进行了测试。测试覆盖率很低,但足以说“这可能有效”:
int[,] array = new int[,] { { 1, 2, 3 }, { 11, 12, 13 }, { 21, 22, 23 } };
var paddedArray = new ZeroPadArray<int>(array);
Debug.Assert(paddedArray[0, 0] == 0);
Debug.Assert(paddedArray[4,4] == 0);
Debug.Assert(paddedArray[2,3] == 13);
最后,为了好玩,我添加了一个不错的小 hack 来创建这些东西需要更少的输入。当你调用一个方法时,编译器通常能够从方法参数中推断出对象的泛型类型。这不适用于构造函数。这就是为什么你需要指定 new ZeroPadArray<int>(array)
即使 array
显然是 int
.
解决此问题的方法是创建第二个非泛型 class,用作创建事物的静态工厂。类似于:
public static class ZeroPadArray
{
public static ZeroPadArray<T> Create<T>(T[,] arrayToPad)
{
return new ZeroPadArray<T>(arrayToPad);
}
}
现在,不用输入:
var paddedArray = new ZeroPadArray<int>(array);
您可以输入:
var paddedArray = ZeroPadArray.Create(array);
为您节省了两个字符的输入(但是,您需要承认输入 <int>
令人沮丧)。