0

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. Example:

|1 2 3|

|4 5 6|

|7 8 9|

Should become

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

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.

  • You should just be able to change your loop range to whatever your padding length is and archive good results. Consider using `for(int y = paddingSize; y < ArrayZeroPad.GetLength(1) - paddingSize; y++)` – DekuDesu Sep 22 '21 at 14:37
  • If you wanted to be creative, you could do this without copying. Just get a class that it handed an array of T and has a `public T this [int][int]` implementation that makes it look like an array X+2 by Y+2 that has zeros (default values) all the way around – Flydog57 Sep 22 '21 at 14:48
  • In the last line of your inner loop you are trying to access memory outside of the bounds of `Array`. The dimensions of `ArrayZeroPad` are longer than those of `Array` but you are indexing up to the lengths of `ArrayZeroPad`'s dimensions. When `x` or `y` goes to a value longer than the length of a dimension of `Array` you are out of bounds. – dmedine Sep 30 '21 at 05:57

4 Answers4

1

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. 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:

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

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

halfer
  • 19,824
  • 17
  • 99
  • 186
Flydog57
  • 6,851
  • 2
  • 17
  • 18
  • I appreciate the amount of effort you put into explaining it for me, thank you! Unfortunately, I can not yet upvote posts –  Sep 25 '21 at 14:54
0

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. 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):

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:

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();
}
Guru Stron
  • 102,774
  • 10
  • 95
  • 132
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 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];
                    }
                }
            }
        }
xrodas
  • 466
  • 2
  • 10
0

You can also solve this using 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. 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).

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.

It's possible that using Buffer.BlockCopy() (which operates on bytes) could be even faster:

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.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276