Как изменить форму массива в С#
У меня есть трехмерный массив байтов в С#, который я прочитал из растрового изображения:
byte[w, h, 3]
Каков самый простой и эффективный способ преобразования этого массива в двумерную (линейную) форму?
byte[w*h, 3]
Другими словами, я хочу сохранить количество каналов (функций), но в линейной форме (а не в квадратной форме)
Позвольте мне проиллюстрировать ввод и желаемый результат:
ввод:
|(r1,g1,b1) (r2,g2,b2) (r3,g3,b3)|
|(r4,g4,b4) (r5,g5,b5) (r6,g6,b6)|
|(r7,g7,b7) (r8,g8,b8) (r9,g9,b9)|
заметим, что arr [0, 0, 0] = r1, arr [0, 0, 1] = g1, arr [0, 0, 2] = b1 и т.д.
и вывод:
|(r1,g1,b1) (r2,g2,b2) (r3,g3,b3) (r4,g4,b4) (r5,g5,b5) (r6,g6,b6) ...|
Ответы
Ответ 1
Кажется, что это нормально, потому что массив уже находится в правильной форме в памяти:
var a = new byte[2, 2, 2] { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } };
var b = new byte[2 * 2, 2];
//sizeof(byte) is obviously 1 here, but I put it there for documentation
Buffer.BlockCopy(a, 0, b, 0, a.Length * sizeof(byte));
Для интересующихся: что делать, если вы действительно хотите перенести 2D-массив в 1D:
byte[,] a = {
{1, 2},
{3, 4},
{5, 6},
};
var b = new byte[a.GetLength(1) * a.GetLength(0)]; //Transpose
const int R_STRIDE1 = 8; //Tune this for your CPU
const int C_STRIDE1 = 8; //Tune this for your CPU
//You should hoist the calls to GetLength() out of the loop unlike what I do here
for (int r1 = 0; r1 < a.GetLength(0); r1 += R_STRIDE1)
for (int c1 = 0; c1 < a.GetLength(1); c1 += C_STRIDE1)
for (int r2 = 0; r2 < R_STRIDE1; r2++)
for (int c2 = 0; c2 < C_STRIDE1; c2++)
{
var r = r1 + r2;
var c = c1 + c2;
if (r < a.GetLength(0) && c < a.GetLength(1))
b[c * a.GetLength(0) + r] = a[r, c];
}
Это должно использовать кеширование в CPU. У меня только выполнено ограниченное тестирование на этом - он все равно может быть медленным. Попробуйте настроить его, если он есть.
Вы можете (несколько нетривиально) распространить это на 3D-массив.
Ответ 2
Buffer.BlockCopy
сделает это. По крайней мере, он работает в этом простом тесте.
byte[, ,] src = new byte[10, 10, 3];
byte[,] dest = new byte[100, 3];
List<byte> srcList = new List<byte>();
Random rnd = new Random();
for (int i = 0; i < 10; ++i)
{
for (int j = 0; j < 10; ++j)
{
for (int k = 0; k < 3; ++k)
{
byte b = (byte)rnd.Next();
src[i, j, k] = b;
srcList.Add(b);
}
}
}
Buffer.BlockCopy(src, 0, dest, 0, 300);
List<byte> destList = new List<byte>();
for (int i = 0; i < 100; ++i)
{
for (int j = 0; j < 3; ++j)
{
destList.Add(dest[i, j]);
}
}
// See if they're in the same order
for (int i = 0; i < srcList.Count; ++i)
{
Console.WriteLine("{0,3:N0} - {1,3:N0}", srcList[i], destList[i]);
if (srcList[i] != destList[i])
{
Console.WriteLine("ERROR!");
}
}
Тем не менее, я бы не использовал Buffer.BlockCopy
таким образом, если бы не был абсолютно уверен, что не было странных случаев с проблемами заполнения и т.д. И хотя Buffer.BlockCopy
, конечно, быстрее, чем эквивалентный явный цикл, не должны существенно влиять на время выполнения вашей программы. Если вы не делаете это преобразование внутри фрагмента кода, который называется очень, очень часто... в этом случае у вас больше проблем.
Я бы предложил написать явный цикл.