【问题标题】:Zero padding a 2D array in C#在 C# 中对二维数组进行零填充
【发布时间】:2021-11-16 00:50:03
【问题描述】:

我目前遇到零填充二维数组的问题。我想将我的数组中的当前数据传输到一个新数组,该数组是完全相同的数组,但它周围的边框为 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.

【问题讨论】:

  • 您应该能够将循环范围更改为您的填充长度,并存档良好的结果。考虑使用for(int y = paddingSize; y &lt; ArrayZeroPad.GetLength(1) - paddingSize; y++)
  • 如果你想有创意,你可以在不复制的情况下做到这一点。只需获取一个类,它传递了一个 T 数组并具有一个 public T this [int][int] 实现,使其看起来像一个数组 X+2 乘 Y+2,它一直都有零(默认值)
  • 在内部循环的最后一行中,您试图访问Array 范围之外的内存。 ArrayZeroPad 的尺寸比 Array 的尺寸长,但您的索引长度达到了 ArrayZeroPad 的尺寸。当xy 的值大于Array 的维度长度时,您超出了范围。

标签: c# arrays padding zero-padding


【解决方案1】:

这不是你要问的(我认为一个完全不同的选择会很有趣)。

这是一个No-Copy版本,适用于任何类型、任何大小的数组。如果原始数组非常大(因为它不需要副本),则它是合适的。

它使用二维索引器,该索引器为边缘上的项目返回默认值 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);

最后,为了好玩,我添加了一个不错的小技巧,让创建这些东西需要更少的输入。当你调用一个方法时,编译器通常能够从方法参数中推断出对象的泛型类型。这不适用于构造函数。这就是为什么您需要指定new ZeroPadArray&lt;int&gt;(array),即使array 显然是int 的数组。

解决这个问题的方法是创建第二个非泛型类,用作创建事物的静态工厂。比如:

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

节省您输入两个字符(但是,您需要承认输入&lt;int&gt; 令人沮丧)。

【讨论】:

  • 感谢您为我解释它所付出的努力,谢谢!不幸的是,我还不能为帖子投票
【解决方案2】:

您也可以使用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() 将 2D 数组视为线性 1D 数组,您必须将副本的目标偏移量指定为从 1D 数组开始的偏移量(按行优先顺序)。

由于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;
}

与往常一样,只有在性能至关重要时才值得担心,即使这样,也只有在您对代码进行基准测试以验证它实际上 更快。​​

【讨论】:

    【解决方案3】:

    您似乎混淆了维度 - Array.GetLength(0) 用于访问Array[i, j] 中的第一个,Array.GetLength(1) 用于第二个。您还可以通过扫描Array 元素并将目标索引调整一个来简化复制,您无需将其他索引显式设置为0,因为它会为您完成(除非您使用stackalloc 和@ 987654321@ 但我非常怀疑情况是否如此):

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

    【讨论】:

      【解决方案4】:
              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];
                          }
                      }
                  }
              }
      

      【讨论】:

      • 成功了,谢谢!
      • 您实际上不需要手动设置0s。
      • 你是对的,这只是为了清楚和解释的目的
      • 你知道有一种方法可以扩展它吗?我可以选择边框需要多厚?
      猜你喜欢
      • 1970-01-01
      • 2014-11-13
      • 1970-01-01
      • 2014-12-23
      • 2018-04-13
      • 2016-05-27
      • 1970-01-01
      • 2012-08-21
      • 1970-01-01
      相关资源
      最近更新 更多