简体   繁体   English

零填充 C# 中的二维数组

[英]Zero padding a 2D array in C#

I currently have an issue with zero padding my 2d Array.我目前有一个零填充我的二维数组的问题。 I want to transfer my current data in my array to a new array, which is the exact same array but with a border of 0's around it.我想将数组中的当前数据传输到一个新数组,该数组是完全相同的数组,但其周围的边界为 0。 Example:例子:

|1 2 3| |1 2 3|

|4 5 6| |4 5 6|

|7 8 9| |7 8 9|

Should become应该成为

|0 0 0 0 0| |0 0 0 0 0|

|0 1 2 3 0| |0 1 2 3 0|

|0 4 5 6 0| |0 4 5 6 0|

|0 7 8 9 0| |0 7 8 9 0|

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

This is what I have come to thus far, but I keep getting stuck on out of bounds errors, is there anyone who could work this out for me with some explanation?到目前为止,这就是我所做的,但我一直陷入越界错误,有没有人可以为我解决一些解释?

Kind regards, D.亲切的问候,D.

This is not quite what you are asking (I thought a completely different alternative would be interesting).这不是你要问的(我认为一个完全不同的选择会很有趣)。

Here is a No-Copy version that works for any type of array, of any size.这是一个No-Copy版本,适用于任何大小的任何类型的数组。 It's appropriate if the original array is quite large (since it doesn't require a copy).如果原始数组非常大(因为它不需要副本),这是合适的。

It uses a 2-dimensional indexer that either returns the default value of T (zero or null) for items on the edge, and uses the original array (with the indexes offset) for non-edge values:它使用二维索引器,为边缘上的项目返回 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];
        }
    }
}

I just tested it with some Debug.Assert calls.我只是用一些Debug.Assert调用测试了它。 The test coverage is weak, but it was good enough to say "this probably works":测试覆盖率很弱,但足以说“这可能有效”:

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

And, finally, for fun, I added a nice little hack to make creating these things require less typing.最后,为了好玩,我添加了一个不错的小技巧,使创建这些东西需要更少的输入。 When you call a method, the compiler is often able to deduce the generic type of the object from the method parameters.当您调用一个方法时,编译器通常能够从方法参数中推断出对象的泛型类型。 This doesn't work for constructors.这对构造函数不起作用。 That's why you need to specify new ZeroPadArray<int>(array) even though array is obviously an array of int .这就是为什么您需要指定new ZeroPadArray<int>(array)即使array显然是一个int数组。

The way to get around this is to create a second, non-generic class that you use as a static factory for creating things.解决这个问题的方法是创建第二个非泛型类,用作创建事物的静态工厂。 Something like:就像是:

public static class ZeroPadArray
{
    public static ZeroPadArray<T> Create<T>(T[,] arrayToPad)
    {
        return new ZeroPadArray<T>(arrayToPad);
    }
}

Now, instead of typing:现在,而不是键入:

var paddedArray = new ZeroPadArray<int>(array);

you can type:你可以输入:

var paddedArray = ZeroPadArray.Create(array);

Saving you two characters of typing (but, you need to admit that typing the <int> is frustrating).为您节省两个输入字符(但是,您需要承认输入<int>令人沮丧)。

It seems that you are confusing dimensions - Array.GetLength(0) is for the first one in access Array[i, j] and Array.GetLength(1) is for the second.似乎您混淆了维度 - Array.GetLength(0)用于访问Array[i, j]的第一个,而Array.GetLength(1)用于第二个。 Also you can simplify copy by just scanning through Array elements and adjusting destination indexes by one, you don't need to explicitly set others to 0 cause it would be done for you (unless you are using stackalloc and skipping local init but I highly doubt that this is the case):您也可以通过扫描Array元素并将目标索引调整为 1 来简化复制,您不需要将其他人显式设置为0因为它会为您完成(除非您使用stackalloc跳过本地初始化,但我非常怀疑情况就是这样):

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

And in the "print" method too - y should be the first dimension and x - second:并且在“打印”方法中 - 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];
                    }
                }
            }
        }

You can also solve this using Array.Copy() .您也可以使用Array.Copy()解决此问题。 If you require highest performance and the arrays are big enough, then this might be faster than explicitly copying each element:如果您需要最高性能并且数组足够大,那么这可能比显式复制每个元素更快:

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

The (r+1)*(w+2)+1 requires some explanation. (r+1)*(w+2)+1需要一些解释。 Array.Copy() treats a 2D array as a linear 1D array, and you must specify the destination offset for the copy as an offset from the start of the 1D array (in row-major order). Array.Copy()将二维数组视为线性一维数组,您必须将副本的目标偏移量指定为距一维数组开头的偏移量(按行优先顺序)。

Since w is the width of the input array and r is the current row of the input array, the destination for the copy of the current input row will be the output row number, (r+1) times the output row width (w+2) , plus 1 for to account for the left-hand column of 0 in the output array.由于w是输入数组的宽度,而r是输入数组的当前行,当前输入行的副本的目的地将是输出行号, (r+1)乘以输出行宽度(w+2) ,加上1 for 以解释输出数组中的左侧列0

It's possible that using Buffer.BlockCopy() (which operates on bytes) could be even faster:使用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;
}

As always, this is only worth worrying about if performance is critical, and even then only after you've benchmarked the code to verify that it actually is faster.一如往常,这是唯一值得担心如果性能是至关重要的,你基准代码来验证它实际上更快后才即使这样。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM