创建线性二维数组的子数组
Posted
技术标签:
【中文标题】创建线性二维数组的子数组【英文标题】:Creating sub-array of linearized 2d-array 【发布时间】:2021-06-09 09:23:59 【问题描述】:我确信解决方案比我想象的要简单,但由于某种原因,我无法理解它。
我正在使用包含 2d 或 3d 数据的线性化(一维)数组。具体来说,我在 Unity DOTS 环境中使用 NativeArrays,但我确信有一个通用的解决方案。
这是我的伪代码用例:
origin[] =
3, 4, 5,
9, 8, 7,
2, 1, 6
originSize = (3, 3)
现在我想创建一个位置为 (1, 1) 的子数组,该数组的大小为 (2, 2)。 我希望得到
subarray[] =
8, 7,
1, 6
我当前的代码如下所示:
for loop over subarray with index...
int x = index % subSize.x;
int y = index / subSize.x;
int tileX = x + subPos.x * subSize.x;
int tileY = y + subPos.y * subSize.y;
int originIndex = tileX + tileY * originSize.x
subArray[index] = originArray[inputIndex];
这不仅会遇到一些输入超出范围的问题,而且速度也很慢(它位于代码的性能关键部分)。我希望有一个 memcopy 替代方案,但由于数据在数组中是零散的,我唯一的想法是逐行进行,这似乎也不是很快。
我试图找到提取子数组的通用解决方案,但找不到任何不使用多维或锯齿数组的解决方案。该解决方案适用于线性阵列并且速度快,这一点很重要。
谢谢!
【问题讨论】:
index / subSize.x;
将始终为 0,因为 int
除法不是吗?
如果索引高于subSize.x
(宽度)则不会。像这样的索引计算对于线性化二维数组来说是很正常的。索引介于 0 和 subSize.x * subSize.y
之间。
【参考方案1】:
我认为应该这样做
public static class ArrayExtensions
public static T[] GetSubArray<T>( this T[] origin, int originDimensionsX, int originDimensionsY, int startPositionX, int startPositionY, int targetDimensionsX, int targetDimensionsY)
// First of all some checks
if(origin == null) return null;
if(origin.Length == 0) return new T[0];
if(originDimensionsX < 0 || originDimensionsY < 0 || originDimensionsX * originDimensionsY != origin.Length) throw new ArgumentOutOfRangeException($"Given dimensions originDimensionsX,originDimensionsY do not match the given array length origin.Length!");
if(startPositionX < 0 || startPositionX > originDimensionsX - 1) throw new ArgumentOutOfRangeException($"Given startPosition startPositionX is outside of the origin dimensions originDimensionsX!");
if(startPositionY < 0 || startPositionY > originDimensionsY - 1) throw new ArgumentOutOfRangeException($"Given startPosition startPositionY is outside of the origin dimensions originDimensionsY!");
if(targetDimensionsX < 0 || targetDimensionsY < 0) throw new ArgumentOutOfRangeException ("TargetDimensions may not be negative!");
if(startPositionX + targetDimensionsX > originDimensionsX || startPositionY + targetDimensionsY > originDimensionsY) throw new ArgumentOutOfRangeException ($"Given TargetDimensions targetDimensionsX,targetDimensionsY starting at startPositionX,startPositionY does not fit into originDimensions originDimensionsX,originDimensionsY!");
var output = new T[targetDimensionsX * targetDimensionsY];
var subIndex = 0;
for(var y = startPositionY; y < startPositionY + targetDimensionsY; y++)
for(var x = startPositionX; x < startPositionX + targetDimensionsX; x++)
output[subIndex++] = origin[x + y * originDimensionsX];
return output;
例子
var origin = new []
0, 1, 2,
3, 4, 5,
6, 7, 8
;
var subArray = origin.GetSubArray(3, 3, 1, 1, 2, 2);
Debug.Log(string.Join(",", subArray));
会
4, 5, 7, 8
由于对于 X 维度,我们总是为大型数组复制 连续 值,它可能比使用内部 for
循环更有效
Array.Copy
public static T[] GetSubArray(this int[] origin, int originDimensionsX, int originDimensionsY, int startPositionX, int startPositionY, int targetDimensionsX, int targetDimensionsY)
// First of all some checks
if(origin == null) return null;
if(origin.Length == 0) return new T[0];
if(originDimensionsX < 0 || originDimensionsY < 0 || originDimensionsX * originDimensionsY != origin.Length) throw new ArgumentOutOfRangeException($"Given dimensions originDimensionsX,originDimensionsY do not match the given array length origin.Length!");
if(startPositionX < 0 || startPositionX > originDimensionsX - 1) throw new ArgumentOutOfRangeException($"Given startPosition startPositionX is outside of the origin dimensions originDimensionsX!");
if(startPositionY < 0 || startPositionY > originDimensionsY - 1) throw new ArgumentOutOfRangeException($"Given startPosition startPositionY is outside of the origin dimensions originDimensionsY!");
if(targetDimensionsX < 0 || targetDimensionsY < 0) throw new ArgumentOutOfRangeException ("TargetDimensions may not be negative!");
if(startPositionX + targetDimensionsX > originDimensionsX || startPositionY + targetDimensionsY > originDimensionsY) throw new ArgumentOutOfRangeException ($"Given TargetDimensions targetDimensionsX,targetDimensionsY starting at startPositionX,startPositionY does not fit into originDimensions originDimensionsX,originDimensionsY!");
var output = new T[targetDimensionsX * targetDimensionsY];
var subIndex = 0;
for(var y = startPositionY; y < startPositionY + targetDimensionsY; y++)
Array.Copy(origin, startPositionX + y * originDimensionsX, output, subIndex, targetDimensionsX);
subIndex += targetDimensionsX;
return output;
或者你也可以下到字节级别,直接使用 Buffer.BlockCopy
(假设现在是 int 或其他 const 大小的基本类型数组 - 不是通用的,你需要知道常量字节大小你的类型!)
const int SIZEOF_INT = sizeof(int);
public static int[] GetSubArray(this int[] origin, int originDimensionsX, int originDimensionsY, int startPositionX, int startPositionY, int targetDimensionsX, int targetDimensionsY)
// First of all some checks
if(origin == null) return null;
if(origin.Length == 0) return new T[0];
if(originDimensionsX < 0 || originDimensionsY < 0 || originDimensionsX * originDimensionsY != origin.Length) throw new ArgumentOutOfRangeException($"Given dimensions originDimensionsX,originDimensionsY do not match the given array length origin.Length!");
if(startPositionX < 0 || startPositionX > originDimensionsX - 1) throw new ArgumentOutOfRangeException($"Given startPosition startPositionX is outside of the origin dimensions originDimensionsX!");
if(startPositionY < 0 || startPositionY > originDimensionsY - 1) throw new ArgumentOutOfRangeException($"Given startPosition startPositionY is outside of the origin dimensions originDimensionsY!");
if(targetDimensionsX < 0 || targetDimensionsY < 0) throw new ArgumentOutOfRangeException ("TargetDimensions may not be negative!");
if(startPositionX + targetDimensionsX > originDimensionsX || startPositionY + targetDimensionsY > originDimensionsY) throw new ArgumentOutOfRangeException ($"Given TargetDimensions targetDimensionsX,targetDimensionsY starting at startPositionX,startPositionY does not fit into originDimensions originDimensionsX,originDimensionsY!");
var output = new int[targetDimensionsX * targetDimensionsY];
var subIndex = 0;
for(var y = startPositionY; y < startPositionY + targetDimensionsY; y++)
// In this case the indices are all referring to bytes instead of array index
Buffer.BlockCopy(origin, (startPositionX + y * originDimensionsX) * SIZEOF_INT, output, subIndex * SIZEOF_INT, targetDimensionsX * SIZEOF_INT);
subIndex += targetDimensionsX;
return output;
注意:在智能手机上输入 -> 未经测试,但我希望思路清晰
【讨论】:
我认为这可能是迄今为止最好的答案,非常感谢。我需要检查我是否可以做BlockCopy
或与NativeArray
类似的事情,但上述解决方案已经非常好并且易于并行化(通过将工作分成块,因为它只依赖于子索引)。
哦,对于本机数组,您也可以只使用NativeArray.Copy
,这又是通用的,您不需要知道字节大小
谢谢,我完全错过了Copy
方法。我主要使用CopyFrom
和CopyTo
方法,它们没有变体来指定要复制的部分。这很有帮助!【参考方案2】:
这是我尝试并为我工作的代码 sn-p。让我知道它是否有帮助: `
// Size for original array
int originSizeX = 4;
int originSizeY = 4;
// Size for sub array
int subArrSizeX = 3;
int subArrSizeY = 3;
// Starting positions of sub-array in original array
int startingPosX = 0;
int StartingPosY = 1;
// Create original array
int[] arr = CreateArr(originSizeX, originSizeY);
// Get sub array
int[] subArr = GetSubArr(arr, subArrSizeX, subArrSizeY, startingPosX, StartingPosY, originSizeX, originSizeY);
private static int[] GetSubArr(int[] arr, int subArrSizeX, int subArrSizeY, int startingPosX, int startingPosY, int originArrSizeX, int originArrSizeY)
int[] subArr = new int[subArrSizeX * subArrSizeY];
int subArrCurrentIndexX = 0;
int subArrCurrentIndexY = 0;
for (int originArrCurrentIndexY = startingPosY; originArrCurrentIndexY < startingPosY + subArrSizeY; originArrCurrentIndexY++)
for (int originArrCurrentIndexX = startingPosX; originArrCurrentIndexX < startingPosX + subArrSizeX; originArrCurrentIndexX++)
// Transform indices from 2D to 1D
int originArrIndex = (originArrCurrentIndexY ) * originArrSizeX + originArrCurrentIndexX ;
int subArrIndex = (subArrCurrentIndexY ) * subArrSizeX + subArrCurrentIndexX ;
subArr[subArrIndex] = arr[originArrIndex];
subArrCurrentIndexX++;
if (subArrCurrentIndexX == subArrSizeX)
subArrCurrentIndexX = 0;
subArrCurrentIndexY++;
return subArr;
private static int[] CreateArr(int originSizeX, int originSizeY)
int[] arr = new int[originSizeX * originSizeY];
for (int i = 0; i < arr.Length; i++)
arr[i] = i;
return arr;
`
【讨论】:
谢谢你,这行得通,但不幸的是速度很慢。出于这个原因,我试图避免嵌套循环。如果没有其他方法,我将不得不处理它,但我仍然希望有更好的表现。我会尝试将您的算法调整为在使用 Unity JobSystem 的并行线程中效果更好的东西(在输出数据上具有连续索引的单循环),也许这已经提供了更好的性能。仍然非常感谢!以上是关于创建线性二维数组的子数组的主要内容,如果未能解决你的问题,请参考以下文章
C 语言数组 ( 验证二维数组内存是线性的 | 打印二维数组 | 以一维数组方式打印二维数组 | 打印二维数组值和地址 )