#c# #arrays #padding #zero-padding
Вопрос:
В настоящее время у меня проблема с заполнением нулем моего 2d-массива. Я хочу перенести свои текущие данные в моем массиве в новый массив, который является точно таким же массивом, но с границей 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();
}
}
Это то, к чему я пришел до сих пор, но я продолжаю зацикливаться на ошибках за пределами границ, есть ли кто-нибудь, кто мог бы разобраться в этом для меня с некоторым объяснением?
С уважением, Д.
Комментарии:
1. Вы просто должны иметь возможность изменять диапазон циклов на любую длину заполнения и архивировать хорошие результаты. Рассмотрите возможность использования
for(int y = paddingSize; y < ArrayZeroPad.GetLength(1) - paddingSize; y )
2. Если бы вы хотели проявить творческий подход, вы могли бы сделать это без копирования. Просто получите класс, который он передал массиву T и имеет
public T this [int][int]
реализацию, которая делает его похожим на массив X 2 на Y 2, который имеет нули (значения по умолчанию) по всему периметру3. В последней строке вашего внутреннего цикла вы пытаетесь получить доступ к памяти за пределами границ
Array
. РазмерыArrayZeroPad
больше, чем уArray
, но вы индексируете до длины размеровArrayZeroPad
. Когдаx
илиy
переходит к значению, превышающему длину измеренияArray
, вы выходите за рамки.
Ответ №1:
Это не совсем то, о чем вы спрашиваете (я подумал, что была бы интересна совершенно другая альтернатива).
Вот версия без копирования, которая работает для любого типа массива любого размера. Это уместно, если исходный массив довольно большой (поскольку для него не требуется копия).
Он использует 2-мерный индексатор, который либо возвращает значение по умолчанию 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<int>(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);
Сохранение двух символов ввода (но вы должны признать, что ввод текста <int>
расстраивает).
Комментарии:
1. Я ценю то количество усилий, которое вы приложили, чтобы объяснить мне это, спасибо! К сожалению, я пока не могу проголосовать за посты
Ответ №2:
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];
}
}
}
}
Комментарии:
1. Это сработало, спасибо!
2. На самом деле вам не нужно устанавливать
0
s вручную.3. Вы правы, это просто для ясности и объяснения целей
4. Не знаете ли вы, как это расширить, чтобы я мог выбрать, какой толщины должна быть граница?
Ответ №3:
Похоже, вы путаете измерения — Array.GetLength(0)
это для первого в доступе Array[i, j]
и Array.GetLength(1)
для второго. Также вы можете упростить копирование, просто сканируя Array
элементы и настраивая целевые индексы по одному, вам не нужно явно устанавливать другие, чтобы 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];
}
}
И в методе «печать» тоже — 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:
Вы также можете решить эту проблему с помощью 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;
}
Как всегда, об этом стоит беспокоиться только в том случае, если производительность критична, и даже тогда только после того, как вы провели сравнительный анализ кода, чтобы убедиться, что он действительно быстрее.